1import { r as reactExports, j as jsxRuntimeExports, R as React } from "./vendor-react-Bay9Y7Lb.js"; 2import { d9 as Image$1, aW as X, ba as Info, b2 as Upload, b7 as Check, f0 as PenLine, cM as RefreshCw, cd as Save, cf as Plus, b5 as Trash2, g2 as Undo, g3 as Redo, dt as ZoomIn, ds as ZoomOut, c4 as RotateCcw, bF as ChevronRight, cC as ChevronDown, bA as Eye, ck as Layers } from "./vendor-D0P4XgNj.js"; 3import { d as defaultBackgroundConfig, a as defaultBackgroundPresets, m as mergeAllConfigsWithGorilla, c as createAnimator, b as defaultClothingGraphics, g as getDefaultBodyActions, e as defaultActionRules, f as defaultPuppetConfigs, B as BackgroundParticlesCanvas, P as PuppetCharacter, p as parseSvgSocketsAndPlugs } from "./StagePuppetEngine-CmAn6HAX.js"; 4import { d as defaultPercussionGraphics, a as defaultMelodicGraphics } from "./InstrumentMappings-UYUlFRMy.js"; 5import { l as GM_INSTRUMENTS, G as GM_PERCUSSION_MAP } from "./utils-CEVb7nN1.js"; 6import { u as useAnimationFrame } from "./vendor-framer-B-4FZk1Z.js"; 7import "./vendor-i18n-BC7ZMXWQ.js"; 8import "./synth-Fr-K52Ox.js"; 9import "./vendor-react-three-BCww3P1x.js"; 10import "./wasm-synth-adapter-DpNGIIyM.js"; 11import "./vendor-spessasynth-DH8n6o_V.js"; 12import "./vendor-audio-D-wTggbx.js"; 13import "./IkMath-D6fi1ppd.js"; 14import "./vendor-gsap-50Lx19ak.js"; 15import "./index-w5lXls15.js"; 16const BODY_PARTS = [ 17 { id: "head", label: "Head", required: true }, 18 { id: "body", label: "Body (Torso)", required: true }, 19 { id: "upper_arm_l", label: "Left Upper Arm (Point Right â)", required: false }, 20 { id: "lower_arm_l", label: "Left Lower Arm (Point Right â)", required: false }, 21 { id: "hand_l", label: "Left Hand (Point Right â)", required: false }, 22 { id: "upper_arm_r", label: "Right Upper Arm (Point Right â)", required: false }, 23 { id: "lower_arm_r", label: "Right Lower Arm (Point Right â)", required: false }, 24 { id: "hand_r", label: "Right Hand (Point Right â)", required: false }, 25 { id: "leg_l", label: "Left Leg (Point Down â)", required: false }, 26 { id: "foot_l", label: "Left Foot", required: false }, 27 { id: "leg_r", label: "Right Leg (Point Down â)", required: false }, 28 { id: "foot_r", label: "Right Foot", required: false } 29]; 30const RiggingBuilder = ({ existingSvg, onApplySvg, onClose }) => { 31 const [images, setImages] = reactExports.useState(() => { 32 const parsed = {}; 33 if (existingSvg) { 34 try { 35 const parser = new DOMParser(); 36 const doc = parser.parseFromString(`<svg>${existingSvg}</svg>`, "image/svg+xml"); 37 BODY_PARTS.forEach((part) => { 38 const group = doc.querySelector(`#${part.id}`); 39 if (group) { 40 const img = group.querySelector("image"); 41 if (img) { 42 const href = img.getAttribute("href"); 43 const width = parseFloat(img.getAttribute("width") || "0"); 44 const height = parseFloat(img.getAttribute("height") || "0"); 45 if (href && href.startsWith("data:image/")) { 46 parsed[part.id] = { dataUrl: href, width, height }; 47 } 48 } 49 } 50 }); 51 } catch (e) { 52 console.error("Error parsing existing SVG", e); 53 } 54 } 55 return parsed; 56 }); 57 const [isGenerating, setIsGenerating] = reactExports.useState(false); 58 const handleFileChange = async (partId, file) => { 59 if (!file || !file.type.startsWith("image/")) return; 60 return new Promise((resolve) => { 61 const reader = new FileReader(); 62 reader.onload = (e) => { 63 var _a; 64 const dataUrl = (_a = e.target) == null ? void 0 : _a.result; 65 const img = new Image(); 66 img.onload = () => { 67 setImages((prev) => ({ 68 ...prev, 69 [partId]: { dataUrl, width: img.width, height: img.height } 70 })); 71 resolve(); 72 }; 73 img.src = dataUrl; 74 }; 75 reader.readAsDataURL(file); 76 }); 77 }; 78 const handleRemove = (partId) => { 79 setImages((prev) => { 80 const next = { ...prev }; 81 delete next[partId]; 82 return next; 83 }); 84 }; 85 const generateSvg = () => { 86 var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x; 87 setIsGenerating(true); 88 try { 89 const bodyW = ((_a = images.body) == null ? void 0 : _a.width) || 120; 90 const bodyH = ((_b = images.body) == null ? void 0 : _b.height) || 150; 91 const headW = ((_c = images.head) == null ? void 0 : _c.width) || 100; 92 const headH = ((_d = images.head) == null ? void 0 : _d.height) || 100; 93 const uawL = ((_e = images.upper_arm_l) == null ? void 0 : _e.width) || 40; 94 const uahL = ((_f = images.upper_arm_l) == null ? void 0 : _f.height) || 15; 95 const lawL = ((_g = images.lower_arm_l) == null ? void 0 : _g.width) || 40; 96 const lahL = ((_h = images.lower_arm_l) == null ? void 0 : _h.height) || 12; 97 const hwL = ((_i = images.hand_l) == null ? void 0 : _i.width) || 20;
98 const hhL = ((_j = images.hand_l) == null ? void 0 : _j.height) || 20; 99 const uawR = ((_k = images.upper_arm_r) == null ? void 0 : _k.width) || 40; 100 const uahR = ((_l = images.upper_arm_r) == null ? void 0 : _l.height) || 15; 101 const lawR = ((_m = images.lower_arm_r) == null ? void 0 : _m.width) || 40; 102 const lahR = ((_n = images.lower_arm_r) == null ? void 0 : _n.height) || 12; 103 const hwR = ((_o = images.hand_r) == null ? void 0 : _o.width) || 20; 104 const hhR = ((_p = images.hand_r) == null ? void 0 : _p.height) || 20; 105 const legwL = ((_q = images.leg_l) == null ? void 0 : _q.width) || 20; 106 const leghL = ((_r = images.leg_l) == null ? void 0 : _r.height) || 60; 107 const footwL = ((_s = images.foot_l) == null ? void 0 : _s.width) || 30; 108 const foothL = ((_t = images.foot_l) == null ? void 0 : _t.height) || 15; 109 const legwR = ((_u = images.leg_r) == null ? void 0 : _u.width) || 20; 110 const leghR = ((_v = images.leg_r) == null ? void 0 : _v.height) || 60; 111 const footwR = ((_w = images.foot_r) == null ? void 0 : _w.width) || 30; 112 const foothR = ((_x = images.foot_r) == null ? void 0 : _x.height) || 15; 113 const shoulderL_X = -bodyW * 0.4; 114 const shoulderL_Y = -bodyH * 0.85; 115 const shoulderR_X = bodyW * 0.4; 116 const shoulderR_Y = -bodyH * 0.85; 117 const elbowL_X = shoulderL_X - uawL; 118 const elbowL_Y = shoulderL_Y; 119 const elbowR_X = shoulderR_X + uawR; 120 const elbowR_Y = shoulderR_Y; 121 const handL_X = elbowL_X - lawL; 122 const handL_Y = elbowL_Y; 123 const handR_X = elbowR_X + lawR; 124 const handR_Y = elbowR_Y; 125 const mouth_X = 0; 126 const mouth_Y = -bodyH - headH * 0.3; 127 let svgParts = ""; 128 svgParts += ` <!-- Invisible standard sockets for auto-discovery --> 129`; 130 svgParts += ` <circle id="socket_mouth" cx="${mouth_X}" cy="${mouth_Y}" r="1" opacity="0" /> 131`; 132 svgParts += ` <circle id="socket_shoulder_l" cx="${shoulderL_X}" cy="${shoulderL_Y}" r="1" opacity="0" /> 133`; 134 svgParts += ` <circle id="socket_shoulder_r" cx="${shoulderR_X}" cy="${shoulderR_Y}" r="1" opacity="0" /> 135`; 136 svgParts += ` <circle id="socket_elbow_l" cx="${elbowL_X}" cy="${elbowL_Y}" r="1" opacity="0" /> 137`; 138 svgParts += ` <circle id="socket_elbow_r" cx="${elbowR_X}" cy="${elbowR_Y}" r="1" opacity="0" /> 139`; 140 svgParts += ` <circle id="socket_hand_l" cx="${handL_X}" cy="${handL_Y}" r="1" opacity="0" /> 141`; 142 svgParts += ` <circle id="socket_hand_r" cx="${handR_X}" cy="${handR_Y}" r="1" opacity="0" /> 143 144`; 145 const getGroupOrEmpty = (id, content) => { 146 return ` <g id="${id}">${content}</g> 147`; 148 }; 149 const legL_X = -bodyW * 0.25; 150 const legL_Y = 0; 151 let legLContent = ""; 152 if (images.leg_l) { 153 legLContent += ` 154 <image href="${images.leg_l.dataUrl}" x="${-legwL / 2}" y="0" width="${legwL}" height="${leghL}" />`; 155 } 156 let footLContent = ""; 157 if (images.foot_l) { 158 footLContent = `<image href="${images.foot_l.dataUrl}" x="${-footwL / 2}" y="${-foothL / 2}" width="${footwL}" height="${foothL}" />`; 159 } 160 legLContent += ` 161 <g id="foot_l" transform="translate(0, ${leghL})">${footLContent}</g> 162 `; 163 svgParts += ` <!-- Left Leg & Foot --> 164`; 165 svgParts += ` <g id="leg_l" transform="translate(${legL_X}, ${legL_Y})">${legLContent}</g> 166 167`; 168 const legR_X = bodyW * 0.25; 169 const legR_Y = 0; 170 let legRContent = ""; 171 if (images.leg_r) { 172 legRContent += ` 173 <image href="${images.leg_r.dataUrl}" x="${-legwR / 2}" y="0" width="${legwR}" height="${leghR}" />`; 174 } 175 let footRContent = ""; 176 if (images.foot_r) { 177 footRContent = `<image href="${images.foot_r.dataUrl}" x="${-footwR / 2}" y="${-foothR / 2}" width="${footwR}" height="${foothR}" />`; 178 } 179 legRContent += ` 180 <g id="foot_r" transform="translate(0, ${leghR})">${footRContent}</g> 181 `; 182 svgParts += ` <!-- Right Leg & Foot --> 183`; 184 svgParts += ` <g id="leg_r" transform="translate(${legR_X}, ${legR_Y})">${legRContent}</g> 185 186`; 187 svgParts += ` <!-- Torso --> 188`; 189 if (images.body) { 190 svgParts += getGroupOrEmpty("body", ` 191 <image href="${images.body.dataUrl}" x="${-bodyW / 2}" y="${-bodyH}" width="${bodyW}" height="${bodyH}" /> 192 `); 193 } else { 194 svgParts += ` <g id="body"></g> 195`; 196 } 197 svgParts += ` <!-- Head --> 198`; 199 if (images.head) { 200 svgParts += getGroupOrEmpty("head", ` 201 <image href="${images.head.dataUrl}" x="${-headW / 2}" y="${-bodyH - headH}" width="${headW}" height="${headH}" /> 202 `); 203 } else { 204 svgParts += ` <g id="head"></g> 205`; 206 } 207 svgParts += ` <!-- Arm hierarchies (flat sibling segments for IK calculations) --> 208`; 209 svgParts += ` <g id="shoulder_l" opacity="0"></g> 210`; 211 svgParts += ` <g id="shoulder_r" opacity="0"></g> 212`; 213 svgParts += ` <g id="elbow_l" opacity="0"></g> 214`; 215 svgParts += ` <g id="elbow_r" opacity="0"></g> 216`; 217 if (images.upper_arm_l) { 218 svgParts += getGroupOrEmpty("upper_arm_l", ` 219 <image href="${images.upper_arm_l.dataUrl}" x="0" y="${-uahL / 2}" width="${uawL}" height="${uahL}" /> 220 `); 221 } else { 222 svgParts += ` <g id="upper_arm_l"></g> 223`; 224 } 225 if (images.upper_arm_r) { 226 svgParts += getGroupOrEmpty("upper_arm_r", ` 227 <image href="${images.upper_arm_r.dataUrl}" x="0" y="${-uahR / 2}" width="${uawR}" height="${uahR}" /> 228 `); 229 } else { 230 svgParts += ` <g id="upper_arm_r"></g> 231`; 232 } 233 if (images.lower_arm_l) { 234 svgParts += getGroupOrEmpty("lower_arm_l", ` 235 <image href="${images.lower_arm_l.dataUrl}" x="0" y="${-lahL / 2}" width="${lawL}" height="${lahL}" /> 236 `); 237 } else { 238 svgParts += ` <g id="lower_arm_l"></g> 239`; 240 } 241 if (images.lower_arm_r) { 242 svgParts += getGroupOrEmpty("lower_arm_r", ` 243 <image href="${images.lower_arm_r.dataUrl}" x="0" y="${-lahR / 2}" width="${lawR}" height="${lahR}" /> 244 `); 245 } else { 246 svgParts += ` <g id="lower_arm_r"></g> 247`; 248 } 249 let handLContent = ""; 250 if (images.hand_l) { 251 handLContent += ` 252 <image href="${images.hand_l.dataUrl}" x="${-hwL / 2}" y="${-hhL / 2}" width="${hwL}" height="${hhL}" />`; 253 } 254 handLContent += ` 255 <!-- Dynamic Instrument Injection Point --> 256 <g id="instrument" transform="translate(0,0)"></g> 257 `; 258 svgParts += ` <g id="hand_l">${handLContent}</g> 259`; 260 if (images.hand_r) { 261 svgParts += getGroupOrEmpty("hand_r", ` 262 <image href="${images.hand_r.dataUrl}" x="${-hwR / 2}" y="${-hhR / 2}" width="${hwR}" height="${hhR}" /> 263 `); 264 } else { 265 svgParts += ` <g id="hand_r"></g> 266`; 267 } 268 onApplySvg(svgParts); 269 } catch (e) { 270 console.error("Error generating SVG", e); 271 alert("An error occurred while generating the SVG."); 272 } finally { 273 setIsGenerating(false); 274 } 275 }; 276 return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed inset-0 bg-black/80 z-50 flex items-center justify-center p-4", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-[#111] border border-zinc-800 rounded-xl shadow-2xl w-full max-w-4xl max-h-[90vh] flex flex-col", children: [
277 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between p-4 border-b border-zinc-800 bg-zinc-900/50", children: [ 278 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 279 /* @__PURE__ */ jsxRuntimeExports.jsxs("h2", { className: "text-lg font-bold text-white flex items-center gap-2", children: [ 280 /* @__PURE__ */ jsxRuntimeExports.jsx(Image$1, { size: 20, className: "text-indigo-400" }), 281 "Rigging Assistant (PNG to SVG Wrapper)" 282 ] }), 283 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-400 mt-1", children: "Upload separate PNG images for body parts. The system will automatically bundle them into a compatible SVG." }) 284 ] }), 285 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "p-2 text-zinc-400 hover:text-white rounded hover:bg-zinc-800 transition-colors", children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { size: 20 }) }) 286 ] }), 287 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 bg-indigo-950/20 border-b border-indigo-500/20 text-indigo-200 text-sm flex gap-3", children: [
288 /* @__PURE__ */ jsxRuntimeExports.jsx(Info, { size: 20, className: "shrink-0 mt-0.5 text-indigo-400" }), 289 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 290 /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Important Rigging Rules:" }), 291 /* @__PURE__ */ jsxRuntimeExports.jsxs("ul", { className: "list-disc ml-5 mt-1 opacity-90 space-y-0.5", children: [ 292 /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [ 293 "All ", 294 /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Arm" }), 295 " pieces (upper, lower, hands) must be drawn horizontally pointing to the right (â)." 296 ] }), 297 /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [ 298 /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Legs" }), 299 " should be drawn vertically pointing down (â)." 300 ] }), 301 /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [ 302 /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Body" }), 303 " and ",
304 /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Head" }), 305 " should face forward." 306 ] }), 307 /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: "Transparent PNGs are highly recommended." }) 308 ] }) 309 ] }) 310 ] }), 311 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto p-4 bg-[#0a0a0a] custom-scrollbar", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-2 md:grid-cols-3 gap-4", children: BODY_PARTS.map((part) => { 312 const hasImage = !!images[part.id]; 313 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `border ${hasImage ? "border-emerald-500/50 bg-emerald-950/10" : "border-zinc-800 bg-[#111]"} rounded-lg p-3 relative transition-colors`, children: [ 314 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs font-bold text-zinc-300 mb-2 truncate", children: [ 315 part.label, 316 " ", 317 part.required && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-rose-500", children: "*" }) 318 ] }),
319 hasImage ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative group rounded bg-black border border-zinc-800 h-24 flex items-center justify-center overflow-hidden", children: [ 320 /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: images[part.id].dataUrl, className: "max-w-full max-h-full object-contain", alt: part.label }), 321 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute inset-0 bg-black/60 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center gap-2", children: [ 322 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "bg-indigo-600 hover:bg-indigo-500 text-white p-2 rounded-full shadow-lg cursor-pointer transition-colors", title: "Replace Image", children: [ 323 /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { size: 16 }), 324 /* @__PURE__ */ jsxRuntimeExports.jsx( 325 "input", 326 { 327 type: "file", 328 accept: "image/png,image/jpeg,image/webp", 329 className: "hidden", 330 onChange: (e) => { 331 var _a; 332 const file = (_a = e.target.files) == null ? void 0 : _a[0]; 333 if (file) handleFileChange(part.id, file); 334 e.target.value = ""; 335 } 336 } 337 ) 338 ] }),
339 /* @__PURE__ */ jsxRuntimeExports.jsx( 340 "button", 341 { 342 onClick: () => handleRemove(part.id), 343 className: "bg-rose-600 hover:bg-rose-500 text-white p-2 rounded-full shadow-lg transition-colors", 344 title: "Remove Image", 345 children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { size: 16 }) 346 } 347 ) 348 ] }) 349 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "border-2 border-dashed border-zinc-700 hover:border-indigo-500 hover:bg-indigo-500/5 rounded h-24 flex flex-col items-center justify-center cursor-pointer transition-colors text-zinc-500 hover:text-indigo-400", children: [ 350 /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { size: 20, className: "mb-2" }), 351 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-medium", children: "Upload PNG" }), 352 /* @__PURE__ */ jsxRuntimeExports.jsx( 353 "input", 354 { 355 type: "file", 356 accept: "image/png,image/jpeg,image/webp", 357 className: "hidden", 358 onChange: (e) => { 359 var _a; 360 const file = (_a = e.target.files) == null ? void 0 : _a[0]; 361 if (file) handleFileChange(part.id, file); 362 e.target.value = ""; 363 } 364 } 365 ) 366 ] }) 367 ] }, part.id); 368 }) }) }), 369 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 border-t border-zinc-800 bg-zinc-900/50 flex justify-end gap-3", children: [
370 /* @__PURE__ */ jsxRuntimeExports.jsx( 371 "button", 372 { 373 onClick: onClose, 374 className: "px-4 py-2 text-sm font-bold text-zinc-400 hover:text-white hover:bg-zinc-800 rounded transition-colors", 375 children: "Cancel" 376 } 377 ), 378 /* @__PURE__ */ jsxRuntimeExports.jsx( 379 "button", 380 { 381 onClick: generateSvg, 382 disabled: isGenerating || !images["head"] || !images["body"], 383 className: "flex items-center gap-2 px-6 py-2 bg-indigo-600 hover:bg-indigo-500 disabled:bg-zinc-800 disabled:text-zinc-500 text-white text-sm font-bold rounded transition-colors", 384 children: isGenerating ? "Generating..." : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ 385 /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { size: 16 }), 386 " Generate & Apply SVG" 387 ] }) 388 } 389 ) 390 ] }) 391 ] }) }); 392}; 393function getFamilyDefaultPlugs(family) { 394 switch (family) { 395 case "family_14_percussive": 396 case "percussion": 397 case "family_1_chromatic_percussion": 398 return ["hand_l", "hand_r"]; 399 case "family_0_pianos": 400 case "family_2_organs": 401 case "family_10_synth_lead": 402 case "family_11_synth_pad": 403 case "family_12_synth_effects": 404 case "keys": 405 return ["keys_l", "keys_r"]; 406 case "family_3_guitars": 407 case "family_4_basses": 408 case "family_5_strings": 409 case "family_6_ensemble": 410 case "family_13_ethnic": 411 case "strings": 412 return ["fret_hand", "strum_hand"]; 413 case "family_7_brass": 414 case "family_8_reed": 415 case "family_9_pipe": 416 case "winds": 417 return ["mouthpiece", "keys_l", "keys_r"]; 418 case "family_15_sound_effects": 419 default: 420 return ["hand_l", "hand_r"]; 421 } 422} 423function PuppetStudio() { 424 var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y; 425 const [activeTab, setActiveTab] = reactExports.useState("characters"); 426 const [backgroundConfig, setBackgroundConfig] = reactExports.useState(defaultBackgroundConfig); 427 const [backgroundPresets, setBackgroundPresets] = reactExports.useState(defaultBackgroundPresets); 428 const [selectedPresetId, setSelectedPresetId] = reactExports.useState(null); 429 const [customBackgrounds, setCustomBackgrounds] = reactExports.useState([]); 430 const [configs, setConfigs] = reactExports.useState({}); 431 const [selectedId, setSelectedId] = reactExports.useState(null); 432 const [instrumentConfigs, setInstrumentConfigs] = reactExports.useState({ melodic: {}, percussion: {} }); 433 const [selectedInstKey, setSelectedInstKey] = reactExports.useState(null); 434 const [clothingConfigs, setClothingConfigs] = reactExports.useState({}); 435 const [selectedClothingId, setSelectedClothingId] = reactExports.useState(null); 436 const [saveStatus, setSaveStatus] = reactExports.useState("idle"); 437 const [loading, setLoading] = reactExports.useState(true); 438 const [jsonError, setJsonError] = reactExports.useState(null); 439 const [showSkeleton, setShowSkeleton] = reactExports.useState(false); 440 const [showActionPaths, setShowActionPaths] = reactExports.useState(false); 441 const [isOptionsCollapsed, setIsOptionsCollapsed] = reactExports.useState(false); 442 const [viewBoxZoom, setViewBoxZoom] = reactExports.useState(1); 443 const [uploadError, setUploadError] = reactExports.useState(null); 444 const [showRiggingBuilder, setShowRiggingBuilder] = reactExports.useState(false); 445 const [selectedPartId, setSelectedPartId] = reactExports.useState(null); 446 const [viewZoom, setViewZoom] = reactExports.useState(1); 447 const [viewPanX, setViewPanX] = reactExports.useState(0); 448 const [viewPanY, setViewPanY] = reactExports.useState(0); 449 const [undoStack, setUndoStack] = reactExports.useState([]); 450 const [redoStack, setRedoStack] = reactExports.useState([]); 451 const [globalActions, setGlobalActions] = reactExports.useState([]); 452 const [globalActionRules, setGlobalActionRules] = reactExports.useState([]); 453 const [selectedActionId, setSelectedActionId] = reactExports.useState(null); 454 const [selectedActionRuleId, setSelectedActionRuleId] = reactExports.useState(null); 455 const [selectedChannelIdx, setSelectedChannelIdx] = reactExports.useState(null); 456 const pushUndo = () => { 457 const snapshot = { 458 configs: JSON.parse(JSON.stringify(configs)), 459 instrumentConfigs: JSON.parse(JSON.stringify(instrumentConfigs)), 460 clothingConfigs: JSON.parse(JSON.stringify(clothingConfigs)), 461 backgroundConfig: JSON.parse(JSON.stringify(backgroundConfig)), 462 backgroundPresets: JSON.parse(JSON.stringify(backgroundPresets)) 463 }; 464 setUndoStack((prev) => [...prev.slice(-49), snapshot]); 465 setRedoStack([]); 466 }; 467 const undo = () => { 468 if (undoStack.length === 0) return; 469 const previous = undoStack[undoStack.length - 1]; 470 const current = { 471 configs: JSON.parse(JSON.stringify(configs)), 472 instrumentConfigs: JSON.parse(JSON.stringify(instrumentConfigs)), 473 clothingConfigs: JSON.parse(JSON.stringify(clothingConfigs)), 474 backgroundConfig: JSON.parse(JSON.stringify(backgroundConfig)), 475 backgroundPresets: JSON.parse(JSON.stringify(backgroundPresets)) 476 }; 477 setRedoStack((prev) => [...prev, current]); 478 setUndoStack((prev) => prev.slice(0, -1)); 479 setConfigs(previous.configs); 480 setInstrumentConfigs(previous.instrumentConfigs); 481 setClothingConfigs(previous.clothingConfigs);
482 setBackgroundConfig(previous.backgroundConfig); 483 setBackgroundPresets(previous.backgroundPresets); 484 }; 485 const redo = () => { 486 if (redoStack.length === 0) return; 487 const next = redoStack[redoStack.length - 1]; 488 const current = { 489 configs: JSON.parse(JSON.stringify(configs)), 490 instrumentConfigs: JSON.parse(JSON.stringify(instrumentConfigs)), 491 clothingConfigs: JSON.parse(JSON.stringify(clothingConfigs)), 492 backgroundConfig: JSON.parse(JSON.stringify(backgroundConfig)), 493 backgroundPresets: JSON.parse(JSON.stringify(backgroundPresets)) 494 }; 495 setUndoStack((prev) => [...prev, current]); 496 setRedoStack((prev) => prev.slice(0, -1)); 497 setConfigs(next.configs); 498 setInstrumentConfigs(next.instrumentConfigs); 499 setClothingConfigs(next.clothingConfigs); 500 setBackgroundConfig(next.backgroundConfig); 501 setBackgroundPresets(next.backgroundPresets); 502 }; 503 const currentConfigToEdit = reactExports.useMemo(() => { 504 if (selectedPresetId === null) return backgroundConfig; 505 const preset = backgroundPresets.find((p) => p.id === selectedPresetId); 506 return preset ? preset.config : backgroundConfig; 507 }, [selectedPresetId, backgroundConfig, backgroundPresets]); 508 const updateActiveConfig = (updater) => { 509 pushUndo(); 510 if (selectedPresetId === null) { 511 setBackgroundConfig((prev) => updater(prev)); 512 } else { 513 setBackgroundPresets((prev) => prev.map((p) => { 514 if (p.id === selectedPresetId) { 515 return { ...p, config: updater(p.config) }; 516 } 517 return p; 518 })); 519 } 520 }; 521 const handleConfigChange = (id, updates) => { 522 setConfigs((prev) => ({ ...prev, [id]: { ...prev[id], ...updates } })); 523 }; 524 const handleIkChange = (id, arm, field, value) => { 525 setConfigs((prev) => { 526 const current = prev[id]; 527 const updatedArm = { ...current[arm] || {}, [field]: value }; 528 return { ...prev, [id]: { ...current, [arm]: updatedArm } }; 529 }); 530 }; 531 const [previewCharId, setPreviewCharId] = reactExports.useState("percussion"); 532 const [previewInstKey, setPreviewInstKey] = reactExports.useState(null); 533 const [previewClothingId, setPreviewClothingId] = reactExports.useState(null); 534 reactExports.useEffect(() => { 535 var _a2; 536 if (activeTab === "instruments" && selectedInstKey) { 537 const type = selectedInstKey.startsWith("m_") ? "melodic" : "percussion"; 538 const index = parseInt(selectedInstKey.split("_")[1]); 539 const instDef = (_a2 = instrumentConfigs[type]) == null ? void 0 : _a2[index]; 540 if (instDef && instDef.characterId) { 541 setPreviewCharId(instDef.characterId); 542 } else { 543 let targetCharId = "family_0_pianos"; 544 if (selectedInstKey.startsWith("p_")) { 545 targetCharId = "percussion"; 546 } else { 547 const program = parseInt(selectedInstKey.split("_")[1]); 548 if (!isNaN(program)) { 549 const familyIndex = Math.floor(program / 8); 550 const familyKeys = [ 551 "family_0_pianos", 552 "family_1_chromatic_percussion", 553 "family_2_organs", 554 "family_3_guitars", 555 "family_4_basses", 556 "family_5_strings", 557 "family_6_ensemble", 558 "family_7_brass", 559 "family_8_reed", 560 "family_9_pipe", 561 "family_10_synth_lead", 562 "family_11_synth_pad", 563 "family_12_synth_effects", 564 "family_13_ethnic", 565 "family_14_percussive", 566 "family_15_sound_effects" 567 ]; 568 targetCharId = familyKeys[familyIndex] || "family_0_pianos"; 569 } 570 } 571 setPreviewCharId(targetCharId); 572 } 573 if (instDef && instDef.clothingId) { 574 setPreviewClothingId(instDef.clothingId); 575 } else { 576 setPreviewClothingId(null); 577 } 578 } 579 }, [selectedInstKey, activeTab, instrumentConfigs]); 580 const [isPlaying, setIsPlaying] = reactExports.useState(false); 581 const [playMode, setPlayMode] = reactExports.useState("instrument"); 582 const [collapsedGroups, setCollapsedGroups] = reactExports.useState({ character: true, clothing: true, accessory: true }); 583 const [expandedClothingParts, setExpandedClothingParts] = reactExports.useState({}); 584 const [expandedCharacterParts, setExpandedCharacterParts] = reactExports.useState({}); 585 const getDisplayClassName = (part) => { 586 var _a2, _b2; 587 const customContent = (_a2 = selectedClothingDef == null ? void 0 : selectedClothingDef.parts) == null ? void 0 : _a2[part]; 588 if (customContent) { 589 const match = customContent.match(/class=["']([^"']+)["']/i); 590 if (match) return ` (${match[1]})`; 591 } 592 const defaultDef = selectedClothingId ? defaultClothingGraphics[selectedClothingId] : null; 593 const defaultContent = (_b2 = defaultDef == null ? void 0 : defaultDef.parts) == null ? void 0 : _b2[part]; 594 if (defaultContent) { 595 const match = defaultContent.match(/class=["']([^"']+)["']/i); 596 if (match) return ` (${match[1]})`; 597 } 598 const clothId = (selectedClothingDef == null ? void 0 : selectedClothingDef.id) || selectedClothingId || "custom";
599 let classLabel = `clothing-${clothId}-${part}`; 600 if (clothId === "tuxedo") { 601 if (part.includes("arm")) classLabel = "clothing-tuxedo-sleeve"; 602 else if (part.includes("leg")) classLabel = "clothing-tuxedo-pants"; 603 } else if (clothId === "dress") { 604 if (part.includes("arm")) classLabel = "clothing-dress-sleeve"; 605 } 606 return ` (${classLabel})`; 607 }; 608 const [tempo, setTempo] = reactExports.useState(120); 609 const [visibility, setVisibility] = reactExports.useState({ 610 head: true, 611 body: true, 612 upper_arm_l: true, 613 lower_arm_l: true, 614 hand_l: true, 615 upper_arm_r: true, 616 lower_arm_r: true, 617 hand_r: true, 618 leg_l: true, 619 leg_r: true, 620 foot_l: true, 621 foot_r: true, 622 instrument: true, 623 clothing_body: true, 624 clothing_head: true, 625 clothing_upper_arm_l: true, 626 clothing_lower_arm_l: true, 627 clothing_hand_l: true, 628 clothing_upper_arm_r: true, 629 clothing_lower_arm_r: true, 630 clothing_hand_r: true, 631 clothing_leg_l: true, 632 clothing_leg_r: true, 633 clothing_foot_l: true, 634 clothing_foot_r: true 635 }); 636 const [editMode, setEditMode] = reactExports.useState("none"); 637 const [draggingPoint, setDraggingPoint] = reactExports.useState(null); 638 const mergedConfigs = reactExports.useMemo(() => mergeAllConfigsWithGorilla(configs), [configs]); 639 const selectedConfig = selectedId ? mergedConfigs[selectedId] : null; 640 const selectedInstType = (selectedInstKey == null ? void 0 : selectedInstKey.startsWith("m_")) ? "melodic" : "percussion"; 641 const selectedInstIndex = selectedInstKey ? parseInt(selectedInstKey.split("_")[1]) : 0; 642 const selectedInstSvg = selectedInstKey ? instrumentConfigs[selectedInstType][selectedInstIndex] : null; 643 const selectedClothingDef = selectedClothingId ? clothingConfigs[selectedClothingId] : null; 644 reactExports.useEffect(() => { 645 const defaultVisibility = { 646 head: true, 647 body: true, 648 upper_arm_l: true, 649 lower_arm_l: true, 650 hand_l: true, 651 upper_arm_r: true, 652 lower_arm_r: true, 653 hand_r: true, 654 leg_l: true, 655 leg_r: true, 656 foot_l: true, 657 foot_r: true, 658 instrument: true, 659 clothing_body: true, 660 clothing_head: true, 661 clothing_upper_arm_l: true, 662 clothing_lower_arm_l: true, 663 clothing_hand_l: true, 664 clothing_upper_arm_r: true, 665 clothing_lower_arm_r: true, 666 clothing_hand_r: true, 667 clothing_leg_l: true, 668 clothing_leg_r: true, 669 clothing_foot_l: true, 670 clothing_foot_r: true 671 }; 672 const configVisibility = (selectedConfig == null ? void 0 : selectedConfig.visibility) || defaultVisibility; 673 const keys = Object.keys(defaultVisibility); 674 const hasMismatch = keys.some((key) => { 675 const localVal = visibility[key] !== false; 676 const configVal = configVisibility[key] !== false; 677 return localVal !== configVal; 678 }); 679 if (hasMismatch) { 680 setVisibility({ 681 ...defaultVisibility, 682 ...configVisibility 683 }); 684 } 685 }, [selectedConfig, visibility]); 686 const editingInstKey = activeTab === "instruments" ? selectedInstKey : previewInstKey; 687 const editingInstType = (editingInstKey == null ? void 0 : editingInstKey.startsWith("m_")) ? "melodic" : "percussion"; 688 const editingInstIndex = editingInstKey ? parseInt(editingInstKey.split("_")[1]) : 0; 689 const [tickId, setTickId] = reactExports.useState(0); 690 const animatorRef = reactExports.useRef(null); 691 const lastEventTimeRef = reactExports.useRef(0); 692 const mockEventsRef = reactExports.useRef([]); 693 const activePreviewConfig = activeTab === "characters" ? selectedConfig : previewCharId ? mergedConfigs[previewCharId] : null; 694 const isInherited = (field, subfield) => { 695 var _a2; 696 if (!selectedConfig || selectedConfig.id === "percussion") return false; 697 const raw = configs[selectedConfig.id]; 698 if (!raw) return false; 699 if (subfield) { 700 return ((_a2 = raw[field]) == null ? void 0 : _a2[subfield]) === void 0; 701 } 702 return raw[field] === void 0; 703 }; 704 const getInstDef = (key) => { 705 var _a2; 706 if (!key) return null; 707 const type = key.startsWith("m_") ? "melodic" : "percussion"; 708 const index = parseInt(key.split("_")[1]); 709 return ((_a2 = instrumentConfigs[type]) == null ? void 0 : _a2[index]) || null; 710 }; 711 const activePreviewInst = activeTab === "characters" ? getInstDef(previewInstKey) : selectedInstSvg; 712 const activePreviewClothing = activeTab === "clothing" ? selectedClothingDef : previewClothingId ? clothingConfigs[previewClothingId] : null; 713 const previewSvgRef = reactExports.useRef(null); 714 const getProjectedCoords = (selector, localPt) => { 715 if (!previewSvgRef.current || !activePreviewConfig) return null; 716 const charScale = activePreviewConfig.scale ?? 1; 717 const anchorX = activePreviewConfig.anchorX ?? 0; 718 const anchorY = activePreviewConfig.anchorY ?? 0; 719 return { 720 x: (localPt.x + anchorX) * charScale, 721 y: (localPt.y + anchorY) * charScale 722 }; 723 }; 724 const getProjectedPt = (selector, localPt) => { 725 const projected = getProjectedCoords(selector, localPt); 726 return projected || { x: localPt.x, y: localPt.y }; 727 }; 728 const getInstrumentLabel = (type, indexStr) => { 729 const num = parseInt(indexStr); 730 if (type === "melodic") return `${num} - ${GM_INSTRUMENTS[num] || `Prog ${num}`}`; 731 return `${num} - ${GM_PERCUSSION_MAP[num] || `Perc ${num}`}`; 732 }; 733 const handleZOrderChange = (part, val) => { 734 const instKey = activeTab === "characters" ? previewInstKey : selectedInstKey; 735 if (!instKey) return; 736 const type = instKey.startsWith("m_") ? "melodic" : "percussion"; 737 const index = parseInt(instKey.split("_")[1]); 738 setInstrumentConfigs((prev) => { 739 const typeObj = prev[type]; 740 const instObj = typeObj[index] || {}; 741 const zo = { ...instObj.zOrder || {} }; 742 zo[part] = val; 743 return { 744 ...prev, 745 [type]: { 746 ...typeObj, 747 [index]: { 748 ...instObj, 749 zOrder: zo 750 } 751 } 752 }; 753 }); 754 }; 755 const handleClothingMouseDown = (jointId, e) => { 756 var _a2, _b2; 757 if (document.activeElement instanceof HTMLElement) { 758 document.activeElement.blur(); 759 } 760 if (activeTab !== "clothing" || !selectedClothingDef) return; 761 e.stopPropagation(); 762 e.preventDefault(); 763 setSelectedPartId(jointId); 764 setExpandedClothingParts((prev) => ({ ...prev, [jointId]: true })); 765 if (e.detail === 2) { 766 pushUndo(); 767 const currentTransform2 = ((_a2 = selectedClothingDef.transforms) == null ? void 0 : _a2[jointId]) || { scaleX: 1, scaleY: 1, rotation: 0 }; 768 setClothingConfigs((prev) => ({ 769 ...prev, 770 [selectedClothingDef.id]: { 771 ...selectedClothingDef, 772 transforms: { 773 ...selectedClothingDef.transforms, 774 [jointId]: { 775 ...currentTransform2, 776 x: 0, 777 y: 0 778 } 779 } 780 } 781 })); 782 return; 783 } 784 pushUndo(); 785 const targetEl = e.target; 786 const wrapper = targetEl.closest(".custom-clothing-mount"); 787 if (!wrapper) return; 788 const svg = wrapper.ownerSVGElement || wrapper.closest("svg"); 789 if (!svg) return; 790 const wrapperCTM = wrapper.getScreenCTM(); 791 if (!wrapperCTM) return; 792 const inverseMatrix = wrapperCTM.inverse(); 793 const pt = svg.createSVGPoint(); 794 pt.x = e.clientX; 795 pt.y = e.clientY; 796 const localP = pt.matrixTransform(inverseMatrix); 797 const currentTransform = ((_b2 = selectedClothingDef.transforms) == null ? void 0 : _b2[jointId]) || { x: 0, y: 0 }; 798 setDraggingPoint({ 799 target: "clothing", 800 jointId, 801 startX: currentTransform.x || 0, 802 startY: currentTransform.y || 0, 803 startLocalX: localP.x, 804 startLocalY: localP.y, 805 inverseMatrix 806 }); 807 }; 808 const handleBodyPartMouseDown = (partId, e) => { 809 var _a2, _b2, _c2; 810 if (document.activeElement instanceof HTMLElement) { 811 document.activeElement.blur(); 812 } 813 if (activeTab !== "characters" || !selectedConfig) return; 814 e.stopPropagation(); 815 e.preventDefault(); 816 setSelectedPartId(partId); 817 if (e.detail === 2) { 818 pushUndo(); 819 handleConfigChange(selectedConfig.id, { 820 transforms: { 821 ...selectedConfig.transforms, 822 [partId]: { 823 ...((_a2 = selectedConfig.transforms) == null ? void 0 : _a2[partId]) || { scaleX: 1, scaleY: 1, rotation: 0 }, 824 x: 0, 825 y: 0 826 } 827 } 828 }); 829 return; 830 } 831 pushUndo(); 832 const svg = previewSvgRef.current; 833 if (!svg) return; 834 const pt = svg.createSVGPoint(); 835 pt.x = e.clientX; 836 pt.y = e.clientY; 837 const svgP = pt.matrixTransform((_b2 = svg.getScreenCTM()) == null ? void 0 : _b2.inverse()); 838 const currentTransform = ((_c2 = selectedConfig.transforms) == null ? void 0 : _c2[partId]) || { x: 0, y: 0 }; 839 setDraggingPoint({ 840 target: "bodyPart", 841 jointId: partId, 842 startX: currentTransform.x || 0, 843 startY: currentTransform.y || 0, 844 mouseStartX: svgP.x, 845 mouseStartY: svgP.y 846 }); 847 }; 848 const handleInstrumentMouseDown = (partType, e) => { 849 if (document.activeElement instanceof HTMLElement) { 850 document.activeElement.blur(); 851 } 852 if (activeTab !== "instruments" || !selectedInstKey) return; 853 e.stopPropagation(); 854 e.preventDefault(); 855 setSelectedPartId(partType === "main" ? "instrument" : "accessory"); 856 const instType = selectedInstKey.startsWith("m_") ? "melodic" : "percussion"; 857 const instIndex = parseInt(selectedInstKey.split("_")[1]); 858 const instDef = instrumentConfigs[instType][instIndex]; 859 if (!instDef) return;
860 let currentDef; 861 if (typeof instDef === "string") { 862 currentDef = { 863 svgContent: instDef, 864 mountPoint: "body" 865 }; 866 } else { 867 currentDef = instDef; 868 } 869 const isAccessory = partType === "accessory"; 870 const currentTransform = isAccessory ? currentDef.accessoryTransform || { x: 0, y: 0, scaleX: 1, scaleY: 1, rotation: 0 } : currentDef.transform || { x: 0, y: 0, scaleX: 1, scaleY: 1, rotation: 0 }; 871 if (e.detail === 2) { 872 pushUndo(); 873 const resetTransform = { 874 ...currentTransform, 875 x: 0, 876 y: 0 877 }; 878 setInstrumentConfigs((prev) => ({ 879 ...prev, 880 [instType]: { 881 ...prev[instType], 882 [instIndex]: { 883 ...currentDef, 884 [isAccessory ? "accessoryTransform" : "transform"]: resetTransform 885 } 886 } 887 })); 888 return; 889 } 890 pushUndo(); 891 const targetEl = e.target; 892 const wrapper = targetEl.closest(".custom-instrument-mount"); 893 if (!wrapper) return; 894 const svg = wrapper.ownerSVGElement || wrapper.closest("svg"); 895 if (!svg) return; 896 const wrapperCTM = wrapper.getScreenCTM(); 897 if (!wrapperCTM) return; 898 const inverseMatrix = wrapperCTM.inverse(); 899 const pt = svg.createSVGPoint(); 900 pt.x = e.clientX; 901 pt.y = e.clientY; 902 const localP = pt.matrixTransform(inverseMatrix); 903 setDraggingPoint({ 904 target: partType === "main" ? "instrument" : "accessory", 905 startX: currentTransform.x || 0, 906 startY: currentTransform.y || 0, 907 startLocalX: localP.x, 908 startLocalY: localP.y, 909 inverseMatrix 910 }); 911 }; 912 const isPlayingRef = reactExports.useRef(isPlaying); 913 isPlayingRef.current = isPlaying; 914 const playModeRef = reactExports.useRef(playMode); 915 playModeRef.current = playMode; 916 const activePreviewConfigRef = reactExports.useRef(activePreviewConfig); 917 activePreviewConfigRef.current = activePreviewConfig; 918 const activePreviewInstRef = reactExports.useRef(activePreviewInst); 919 activePreviewInstRef.current = activePreviewInst; 920 const tempoRef = reactExports.useRef(tempo); 921 tempoRef.current = tempo; 922 const globalActionsRef = reactExports.useRef(globalActions); 923 globalActionsRef.current = globalActions; 924 const selectedActionIdRef = reactExports.useRef(selectedActionId); 925 selectedActionIdRef.current = selectedActionId; 926 reactExports.useEffect(() => { 927 if (activePreviewConfig) { 928 animatorRef.current = createAnimator(activePreviewConfig); 929 } else { 930 animatorRef.current = null; 931 } 932 }, [activePreviewConfig, activeTab]); 933 useAnimationFrame((time) => { 934 const currentConfig = activePreviewConfigRef.current; 935 const currentInst = activePreviewInstRef.current; 936 const currentIsPlaying = isPlayingRef.current; 937 const currentTempo = tempoRef.current; 938 const currentPlayMode = playModeRef.current; 939 if (!animatorRef.current || !currentConfig) return; 940 animatorRef.current.forceWave = currentIsPlaying && currentPlayMode === "wave" && activeTab !== "actions"; 941 animatorRef.current.disableRandomIdle = true; 942 if (activeTab === "actions") { 943 animatorRef.current.actionEngine.setActions(globalActionsRef.current); 944 if (currentIsPlaying) { 945 animatorRef.current.actionEngine.forceActionId = selectedActionIdRef.current; 946 } else { 947 animatorRef.current.actionEngine.forceActionId = null; 948 animatorRef.current.actionEngine.clearActive(); 949 } 950 } else { 951 if (currentIsPlaying && currentPlayMode === "wave") { 952 animatorRef.current.actionEngine.forceActionId = "idle_wave"; 953 } else { 954 animatorRef.current.actionEngine.forceActionId = null; 955 if (!currentIsPlaying) { 956 animatorRef.current.actionEngine.clearActive(); 957 } 958 } 959 } 960 let activeEvents = []; 961 if (currentIsPlaying && currentPlayMode !== "wave" && activeTab !== "actions") { 962 const interval = 6e4 / currentTempo; 963 if (time - lastEventTimeRef.current > interval) { 964 const noteNum = 40 + Math.floor(Math.random() * 40); 965 mockEventsRef.current = [{ 966 noteNumber: noteNum, 967 velocity: 100, 968 // Hard hit 969 startTime: time, 970 duration: 200 971 }]; 972 lastEventTimeRef.current = time; 973 } 974 activeEvents = mockEventsRef.current.filter((e) => time < e.startTime + e.duration); 975 mockEventsRef.current = activeEvents; 976 } 977 const isSongPlaying = activeTab === "actions" ? currentIsPlaying : currentIsPlaying && currentPlayMode !== "wave"; 978 animatorRef.current.tick(time, activeEvents, currentTempo / 60, isSongPlaying, currentInst); 979 setTickId((prev) => prev + 1); 980 }); 981 const containerRef = reactExports.useRef(null); 982 reactExports.useEffect(() => { 983 const el = containerRef.current; 984 if (!el) return; 985 const handleWheel = (e) => { 986 e.preventDefault(); 987 const zoomIntensity = 0.1;
988 setViewZoom((prev) => { 989 const delta = -e.deltaY; 990 let nextZoom = prev + (delta > 0 ? zoomIntensity : -zoomIntensity) * prev; 991 nextZoom = Math.max(0.25, Math.min(8, nextZoom)); 992 return nextZoom; 993 }); 994 }; 995 el.addEventListener("wheel", handleWheel, { passive: false }); 996 return () => { 997 el.removeEventListener("wheel", handleWheel); 998 }; 999 }, [activePreviewConfig, activeTab]); 1000 reactExports.useEffect(() => { 1001 const handleGlobalKeyDown = (e) => { 1002 var _a2, _b2, _c2, _d2, _e2, _f2, _g2, _h2, _i2, _j2, _k2, _l2, _m2, _n2; 1003 if (((_a2 = document.activeElement) == null ? void 0 : _a2.tagName) === "INPUT" || ((_b2 = document.activeElement) == null ? void 0 : _b2.tagName) === "TEXTAREA" || ((_c2 = document.activeElement) == null ? void 0 : _c2.tagName) === "SELECT") { 1004 return; 1005 } 1006 if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "z") { 1007 e.preventDefault(); 1008 undo(); 1009 } else if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "y") { 1010 e.preventDefault(); 1011 redo(); 1012 } 1013 if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key)) { 1014 if (!selectedPartId) return; 1015 e.preventDefault(); 1016 const step = e.shiftKey ? 10 : 1; 1017 let dx = 0; 1018 let dy = 0; 1019 if (e.key === "ArrowLeft") dx = -step; 1020 if (e.key === "ArrowRight") dx = step; 1021 if (e.key === "ArrowUp") dy = -step; 1022 if (e.key === "ArrowDown") dy = step; 1023 pushUndo(); 1024 if (activeTab === "clothing" && selectedClothingDef) { 1025 if (editMode.startsWith("clothing_plug_")) { 1026 const parts = editMode.split("_"); 1027 const pointKey = parts[2]; 1028 const partName = parts.slice(3).join("_"); 1029 const currentPlugs = selectedClothingDef.plugs || {}; 1030 const partPlugs = currentPlugs[partName] || {}; 1031 const val = partPlugs[pointKey] || { x: 0, y: 0 }; 1032 const newX = (val.x || 0) + dx; 1033 const newY = (val.y || 0) + dy; 1034 setClothingConfigs((prev) => ({ 1035 ...prev, 1036 [selectedClothingDef.id]: { 1037 ...selectedClothingDef, 1038 plugs: { 1039 ...currentPlugs, 1040 [partName]: { 1041 ...partPlugs, 1042 [pointKey]: { x: newX, y: newY } 1043 } 1044 } 1045 } 1046 })); 1047 } else { 1048 const currentTransform = ((_d2 = selectedClothingDef.transforms) == null ? void 0 : _d2[selectedPartId]) || { x: 0, y: 0, scaleX: 1, scaleY: 1, rotation: 0 }; 1049 let localDx = dx; 1050 let localDy = dy; 1051 const wrapper = document.querySelector(`.custom-clothing-mount[data-target-joint="${selectedPartId}"]`); 1052 if (wrapper) { 1053 const matrix = (_g2 = (_f2 = (_e2 = wrapper.transform) == null ? void 0 : _e2.baseVal) == null ? void 0 : _f2.consolidate()) == null ? void 0 : _g2.matrix; 1054 if (matrix) { 1055 const a = matrix.a; 1056 const b = matrix.b; 1057 const denom = a * a + b * b; 1058 if (denom > 1e-4) { 1059 localDx = (dx * a + dy * b) / denom; 1060 localDy = (dy * a - dx * b) / denom; 1061 } 1062 } 1063 } 1064 setClothingConfigs((prev) => ({ 1065 ...prev, 1066 [selectedClothingDef.id]: { 1067 ...selectedClothingDef, 1068 transforms: { 1069 ...selectedClothingDef.transforms, 1070 [selectedPartId]: { 1071 ...currentTransform, 1072 x: (currentTransform.x || 0) + localDx, 1073 y: (currentTransform.y || 0) + localDy 1074 } 1075 } 1076 } 1077 })); 1078 } 1079 } else if (activeTab === "characters" && selectedConfig) { 1080 if (editMode.startsWith("socket_")) { 1081 const socketName = editMode.replace("socket_", ""); 1082 const svgCoords = parseSvgSocketsAndPlugs(selectedConfig.svgContent); 1083 const svgVal = svgCoords[`socket_${socketName}`]; 1084 const configVal = (_h2 = selectedConfig.sockets) == null ? void 0 : _h2[socketName]; 1085 const val = configVal || svgVal || { x: 0, y: 0 }; 1086 const newX = (val.x || 0) + dx; 1087 const newY = (val.y || 0) + dy; 1088 const currentSockets = selectedConfig.sockets || {}; 1089 handleConfigChange(selectedConfig.id, { 1090 sockets: { 1091 ...currentSockets, 1092 [socketName]: { x: newX, y: newY } 1093 } 1094 }); 1095 } else { 1096 const currentTransform = ((_i2 = selectedConfig.transforms) == null ? void 0 : _i2[selectedPartId]) || { x: 0, y: 0, scaleX: 1, scaleY: 1, rotation: 0 }; 1097 handleConfigChange(selectedConfig.id, { 1098 transforms: { 1099 ...selectedConfig.transforms, 1100 [selectedPartId]: { 1101 ...currentTransform, 1102 x: (currentTransform.x || 0) + dx, 1103 y: (currentTransform.y || 0) + dy 1104 } 1105 } 1106 }); 1107 } 1108 } else if (activeTab === "instruments" && selectedInstKey) { 1109 const instType = selectedInstKey.startsWith("m_") ? "melodic" : "percussion"; 1110 const instIndex = parseInt(selectedInstKey.split("_")[1]); 1111 const instObj = instrumentConfigs[instType][instIndex]; 1112 if (instObj) { 1113 let currentDef; 1114 if (typeof instObj === "string") { 1115 currentDef = { 1116 svgContent: instObj, 1117 mountPoint: "body" 1118 }; 1119 } else { 1120 currentDef = instObj; 1121 } 1122 if (editMode.startsWith("plug_")) { 1123 const plugName = editMode.replace("plug_", ""); 1124 const svgCoords = parseSvgSocketsAndPlugs(currentDef.svgContent); 1125 const svgVal = svgCoords[`plug_${plugName}`]; 1126 const configVal = (_j2 = currentDef.plugs) == null ? void 0 : _j2[plugName]; 1127 const val = configVal || svgVal || { x: 0, y: 0 }; 1128 const newX = (val.x || 0) + dx; 1129 const newY = (val.y || 0) + dy; 1130 const updated = { 1131 ...currentDef, 1132 plugs: { 1133 ...currentDef.plugs || {}, 1134 [plugName]: { ...((_k2 = currentDef.plugs) == null ? void 0 : _k2[plugName]) || {}, x: newX, y: newY } 1135 } 1136 }; 1137 setInstrumentConfigs((prev) => ({ 1138 ...prev, 1139 [instType]: { 1140 ...prev[instType], 1141 [instIndex]: updated 1142 } 1143 })); 1144 } else { 1145 const isAccessory = selectedPartId === "accessory"; 1146 const transformField = isAccessory ? "accessoryTransform" : "transform"; 1147 const currentTransform = currentDef[transformField] || { x: 0, y: 0, scaleX: 1, scaleY: 1, rotation: 0 }; 1148 let localDx = dx; 1149 let localDy = dy; 1150 const partType = isAccessory ? "accessory" : "main"; 1151 const wrapper = document.querySelector(`.custom-instrument-mount[data-instrument-part="${partType}"]`); 1152 if (wrapper) { 1153 const matrix = (_n2 = (_m2 = (_l2 = wrapper.transform) == null ? void 0 : _l2.baseVal) == null ? void 0 : _m2.consolidate()) == null ? void 0 : _n2.matrix; 1154 if (matrix) { 1155 const a = matrix.a; 1156 const b = matrix.b; 1157 const denom = a * a + b * b; 1158 if (denom > 1e-4) { 1159 localDx = (dx * a + dy * b) / denom; 1160 localDy = (dy * a - dx * b) / denom; 1161 } 1162 } 1163 } 1164 setInstrumentConfigs((prev) => ({ 1165 ...prev, 1166 [instType]: { 1167 ...prev[instType], 1168 [instIndex]: { 1169 ...currentDef, 1170 [transformField]: { 1171 ...currentTransform, 1172 x: (currentTransform.x || 0) + localDx, 1173 y: (currentTransform.y || 0) + localDy 1174 } 1175 } 1176 } 1177 })); 1178 } 1179 } 1180 } 1181 } 1182 }; 1183 window.addEventListener("keydown", handleGlobalKeyDown); 1184 return () => { 1185 window.removeEventListener("keydown", handleGlobalKeyDown); 1186 }; 1187 }, [selectedPartId, activeTab, configs, instrumentConfigs, clothingConfigs, selectedClothingDef, selectedConfig, editMode, selectedInstKey, pushUndo, handleConfigChange]); 1188 reactExports.useEffect(() => { 1189 if (activeTab === "instruments") { 1190 setSelectedPartId("instrument"); 1191 } else if (activeTab === "clothing") { 1192 setSelectedPartId("body"); 1193 } else { 1194 setSelectedPartId("body"); 1195 } 1196 }, [activeTab]); 1197 reactExports.useEffect(() => { 1198 loadConfigs(); 1199 }, []); 1200 const loadConfigs = async () => {
1201 setLoading(true); 1202 try { 1203 const [charRes, instRes, clothRes, bgRes, presetsRes] = await Promise.all([ 1204 fetch("/api/admin/settings/stage_characters_config", { headers: { "Authorization": "Bearer " + localStorage.getItem("admin_token") } }), 1205 fetch("/api/admin/settings/stage_instruments_config", { headers: { "Authorization": "Bearer " + localStorage.getItem("admin_token") } }), 1206 fetch("/api/admin/settings/stage_clothing_config", { headers: { "Authorization": "Bearer " + localStorage.getItem("admin_token") } }), 1207 fetch("/api/admin/settings/stage_background_config", { headers: { "Authorization": "Bearer " + localStorage.getItem("admin_token") } }), 1208 fetch("/api/admin/settings/stage_background_presets", { headers: { "Authorization": "Bearer " + localStorage.getItem("admin_token") } }) 1209 ]); 1210 if (charRes.ok) { 1211 const data = await charRes.json(); 1212 if (data && data.value && Object.keys(data.value).length > 0) { 1213 setConfigs(data.value); 1214 setSelectedId(Object.keys(data.value)[0]); 1215 setPreviewCharId(Object.keys(data.value)[0]); 1216 } else seedDefaults(); 1217 } else seedDefaults(); 1218 if (instRes.ok) { 1219 const data = await instRes.json(); 1220 if (data && data.value && data.value.melodic && data.value.percussion) { 1221 setInstrumentConfigs(data.value); 1222 setSelectedInstKey("m_0"); 1223 } else seedInstDefaults(); 1224 } else seedInstDefaults(); 1225 if (clothRes.ok) { 1226 const data = await clothRes.json(); 1227 if (data && data.value && Object.keys(data.value).length > 0) { 1228 const merged = { ...defaultClothingGraphics }; 1229 Object.entries(data.value).forEach(([id, savedDef]) => { 1230 if (merged[id]) { 1231 merged[id] = { 1232 ...merged[id], 1233 ...savedDef, 1234 parts: { 1235 ...merged[id].parts, 1236 ...savedDef.parts 1237 }, 1238 gluing: (() => { 1239 const defaultGluing = merged[id].gluing || {}; 1240 const savedGluing = savedDef.gluing || {}; 1241 const mergedGluing = { ...defaultGluing }; 1242 Object.entries(savedGluing).forEach(([part, partGlue]) => { 1243 mergedGluing[part] = { 1244 ...defaultGluing[part] || {}, 1245 ...partGlue 1246 }; 1247 }); 1248 return mergedGluing; 1249 })() 1250 }; 1251 } else { 1252 merged[id] = savedDef; 1253 } 1254 }); 1255 setClothingConfigs(merged); 1256 setSelectedClothingId(Object.keys(merged)[0]); 1257 setPreviewClothingId(Object.keys(merged)[0]); 1258 } else seedClothingDefaults(); 1259 } else seedClothingDefaults(); 1260 if (bgRes.ok) { 1261 const data = await bgRes.json(); 1262 if (data && data.value) { 1263 setBackgroundConfig(data.value); 1264 } else setBackgroundConfig(defaultBackgroundConfig); 1265 } else setBackgroundConfig(defaultBackgroundConfig); 1266 if (presetsRes.ok) { 1267 const data = await presetsRes.json(); 1268 if (data && data.value && Array.isArray(data.value)) { 1269 setBackgroundPresets(data.value); 1270 } else setBackgroundPresets(defaultBackgroundPresets); 1271 } else setBackgroundPresets(defaultBackgroundPresets); 1272 const customBgRes = await fetch("/api/admin/backgrounds", { headers: { "Authorization": "Bearer " + localStorage.getItem("admin_token") } }); 1273 if (customBgRes.ok) { 1274 let customBgs = await customBgRes.json(); 1275 const presetBgs = defaultBackgroundPresets.map((p) => { 1276 var _a2; 1277 return (_a2 = p.config.image) == null ? void 0 : _a2.url; 1278 }).filter(Boolean); 1279 customBgs = [.../* @__PURE__ */ new Set([...presetBgs, ...customBgs])]; 1280 setCustomBackgrounds(customBgs); 1281 } 1282 } catch (e) { 1283 console.error("Failed to load configs", e); 1284 seedDefaults(); 1285 seedInstDefaults(); 1286 seedClothingDefaults(); 1287 } finally {
1288 setLoading(false); 1289 } 1290 }; 1291 reactExports.useEffect(() => { 1292 if (Object.keys(configs).length > 0 && globalActions.length === 0) { 1293 const firstWithActions = Object.values(configs).find((c) => c.actions && c.actions.length > 0); 1294 setGlobalActions((firstWithActions == null ? void 0 : firstWithActions.actions) ? JSON.parse(JSON.stringify(firstWithActions.actions)) : getDefaultBodyActions()); 1295 const firstWithRules = Object.values(configs).find((c) => c.actionRules && c.actionRules.length > 0); 1296 setGlobalActionRules((firstWithRules == null ? void 0 : firstWithRules.actionRules) ? JSON.parse(JSON.stringify(firstWithRules.actionRules)) : defaultActionRules.map((r) => ({ ...r }))); 1297 } 1298 }, [configs]); 1299 const seedDefaults = () => { 1300 setConfigs(defaultPuppetConfigs); 1301 setSelectedId(Object.keys(defaultPuppetConfigs)[0]); 1302 setPreviewCharId(Object.keys(defaultPuppetConfigs)[0]); 1303 }; 1304 const seedInstDefaults = () => { 1305 setInstrumentConfigs({ melodic: { ...defaultMelodicGraphics }, percussion: { ...defaultPercussionGraphics } }); 1306 setSelectedInstKey("m_0"); 1307 }; 1308 const seedClothingDefaults = () => { 1309 setClothingConfigs(defaultClothingGraphics); 1310 setSelectedClothingId(Object.keys(defaultClothingGraphics)[0]); 1311 setPreviewClothingId(Object.keys(defaultClothingGraphics)[0]); 1312 }; 1313 const handleSave = async () => { 1314 setSaveStatus("saving"); 1315 try { 1316 await Promise.all([ 1317 fetch("/api/admin/settings/stage_characters_config", { 1318 method: "POST", 1319 headers: { "Content-Type": "application/json", "Authorization": "Bearer " + localStorage.getItem("admin_token") }, 1320 body: JSON.stringify({ value: configs }) 1321 }), 1322 fetch("/api/admin/settings/stage_instruments_config", { 1323 method: "POST", 1324 headers: { "Content-Type": "application/json", "Authorization": "Bearer " + localStorage.getItem("admin_token") }, 1325 body: JSON.stringify({ value: instrumentConfigs }) 1326 }), 1327 fetch("/api/admin/settings/stage_clothing_config", { 1328 method: "POST", 1329 headers: { "Content-Type": "application/json", "Authorization": "Bearer " + localStorage.getItem("admin_token") }, 1330 body: JSON.stringify({ value: clothingConfigs }) 1331 }), 1332 fetch("/api/admin/settings/stage_background_config", { 1333 method: "POST", 1334 headers: { "Content-Type": "application/json", "Authorization": "Bearer " + localStorage.getItem("admin_token") }, 1335 body: JSON.stringify({ value: backgroundConfig }) 1336 }), 1337 fetch("/api/admin/settings/stage_background_presets", { 1338 method: "POST", 1339 headers: { "Content-Type": "application/json", "Authorization": "Bearer " + localStorage.getItem("admin_token") }, 1340 body: JSON.stringify({ value: backgroundPresets }) 1341 }) 1342 ]); 1343 setSaveStatus("saved"); 1344 setTimeout(() => setSaveStatus("idle"), 3e3); 1345 } catch (e) { 1346 console.error(e); 1347 setSaveStatus("failed"); 1348 setTimeout(() => setSaveStatus("idle"), 3e3); 1349 } 1350 }; 1351 const handleSvgUpload = (e, id) => { 1352 var _a2; 1353 const file = (_a2 = e.target.files) == null ? void 0 : _a2[0]; 1354 if (!file) return; 1355 const reader = new FileReader(); 1356 reader.onload = (event) => { 1357 var _a3; 1358 try { 1359 const content = (_a3 = event.target) == null ? void 0 : _a3.result; 1360 if (!content.includes("<svg") || !content.includes("</svg>")) { 1361 setUploadError("Invalid SVG file format"); 1362 return; 1363 } 1364 const match = content.match(/<svg[^>]*>([\s\S]*?)<\/svg>/i); 1365 const svgInner = match ? match[1] : content; 1366 handleConfigChange(id, { svgContent: svgInner }); 1367 setUploadError(null); 1368 } catch (err) { 1369 setUploadError("Failed to parse SVG file"); 1370 } 1371 }; 1372 reader.readAsText(file); 1373 }; 1374 const handleInstSvgUpload = (e) => { 1375 var _a2; 1376 const file = (_a2 = e.target.files) == null ? void 0 : _a2[0]; 1377 if (!file || !selectedInstKey) return; 1378 const reader = new FileReader(); 1379 reader.onload = (event) => { 1380 var _a3;
1381 try { 1382 const content = (_a3 = event.target) == null ? void 0 : _a3.result; 1383 if (!content.includes("<svg") || !content.includes("</svg>")) { 1384 setUploadError("Invalid SVG file format"); 1385 return; 1386 } 1387 const match = content.match(/<svg[^>]*>([\s\S]*?)<\/svg>/i); 1388 const svgInner = match ? match[1] : content; 1389 const isAccessory = selectedPartId === "accessory"; 1390 setInstrumentConfigs((prev) => { 1391 const typeObj = prev[selectedInstType]; 1392 const instObj = typeObj[selectedInstIndex]; 1393 let currentDef; 1394 if (typeof instObj === "string") { 1395 currentDef = { 1396 svgContent: instObj, 1397 mountPoint: "body" 1398 }; 1399 } else if (!instObj) { 1400 currentDef = { 1401 svgContent: "", 1402 mountPoint: "body" 1403 }; 1404 } else { 1405 currentDef = instObj; 1406 } 1407 if (isAccessory) { 1408 currentDef = { 1409 ...currentDef, 1410 accessorySvg: svgInner, 1411 accessoryMountPoint: currentDef.accessoryMountPoint || "hand_r" 1412 }; 1413 } else { 1414 currentDef = { 1415 ...currentDef, 1416 svgContent: svgInner 1417 }; 1418 } 1419 return { 1420 ...prev, 1421 [selectedInstType]: { 1422 ...typeObj, 1423 [selectedInstIndex]: currentDef 1424 } 1425 }; 1426 }); 1427 setUploadError(null); 1428 } catch (err) { 1429 setUploadError("Failed to parse SVG file"); 1430 } 1431 }; 1432 reader.readAsText(file); 1433 }; 1434 const handleGlobalBgUpload = async (e) => { 1435 var _a2; 1436 const file = (_a2 = e.target.files) == null ? void 0 : _a2[0]; 1437 if (!file) return; 1438 setUploadError(null); 1439 const formData = new FormData(); 1440 formData.append("background", file); 1441 try { 1442 const res = await fetch("/api/admin/upload-global-background", { 1443 method: "POST", 1444 headers: { 1445 "Authorization": "Bearer " + localStorage.getItem("admin_token") 1446 }, 1447 body: formData 1448 }); 1449 if (!res.ok) { 1450 const errData = await res.json(); 1451 throw new Error(errData.error || "Upload failed"); 1452 } 1453 const data = await res.json(); 1454 setCustomBackgrounds((prev) => [...prev, data.url]); 1455 updateActiveConfig((prev) => ({ 1456 ...prev, 1457 type: "image", 1458 image: { 1459 ...prev.image || { size: "cover", position: "center center", repeat: "no-repeat", blur: 0 }, 1460 url: data.url 1461 } 1462 })); 1463 alert("Custom background uploaded successfully. You can select it in the gallery below."); 1464 } catch (err) { 1465 setUploadError(err.message || "Failed to upload image"); 1466 } 1467 }; 1468 const handleDeleteGlobalBg = async (bgUrl) => { 1469 var _a2; 1470 if (!confirm("Are you sure you want to delete this custom background option from the gallery?")) return; 1471 const filename = bgUrl.split("/").pop(); 1472 if (!filename) return; 1473 try { 1474 const res = await fetch(`/api/admin/backgrounds/${filename}`, { 1475 method: "DELETE", 1476 headers: { 1477 "Authorization": "Bearer " + localStorage.getItem("admin_token") 1478 } 1479 }); 1480 if (!res.ok) { 1481 const errData = await res.json(); 1482 throw new Error(errData.error || "Delete failed"); 1483 } 1484 setCustomBackgrounds((prev) => prev.filter((url) => url !== bgUrl)); 1485 if (((_a2 = currentConfigToEdit.image) == null ? void 0 : _a2.url) === bgUrl) { 1486 updateActiveConfig((prev) => ({ 1487 ...prev, 1488 type: "image", 1489 image: { 1490 ...prev.image || {}, 1491 url: "/assets/visualizers/orchestra/background-forest.webp" 1492 } 1493 })); 1494 } 1495 alert("Custom background deleted successfully."); 1496 } catch (err) { 1497 setUploadError(err.message || "Failed to delete background"); 1498 } 1499 }; 1500 const handleJsonUpdate = (id, jsonStr) => { 1501 try { 1502 const parsed = JSON.parse(jsonStr); 1503 handleConfigChange(id, { noteMap: parsed }); 1504 setJsonError(null); 1505 } catch (e) { 1506 setJsonError(e.message); 1507 } 1508 }; 1509 const handleAdd = () => { 1510 const id = `new_puppet_${Date.now()}`; 1511 const defaultCfg = Object.values(defaultPuppetConfigs)[0]; 1512 setConfigs((prev) => ({ ...prev, [id]: { ...defaultCfg, id, name: "New Puppet" } })); 1513 setSelectedId(id); 1514 }; 1515 const handleDelete = (id) => { 1516 if (!confirm("Are you sure?")) return; 1517 const newConfigs = { ...configs }; 1518 delete newConfigs[id]; 1519 setConfigs(newConfigs); 1520 if (selectedId === id) setSelectedId(Object.keys(newConfigs)[0] || null); 1521 }; 1522 if (loading) return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-8 text-zinc-400", children: "Loading Puppet Studio..." }); 1523 const JOINTS_LIST = ["body", "head", "foot_r", "foot_l", "hand_l", "hand_r", "upper_arm_l", "upper_arm_r", "lower_arm_l", "lower_arm_r", "leg_l", "leg_r"]; 1524 const TRIGGER_LABELS_MAP = { playing: "While Playing", idle: "While Idle", note_on: "On Note", always: "Always" }; 1525 const WAVEFORM_LABELS_MAP = { sine: "Sine", cosine: "Cosine", triangle: "Triangle", abs_sine: "Abs Sine
1525(Tap)" }; 1526 const addBodyAction = () => { 1527 pushUndo(); 1528 const newId = `custom_${Date.now()}`; 1529 const a = { id: newId, name: "New Action", category: "body", trigger: "playing", priority: 50, duration: 2e3, tempoSync: false, loop: true, blendInMs: 500, blendOutMs: 500, channels: [], affectedJoints: [] }; 1530 setGlobalActions((prev) => [...prev, a]); 1531 setSelectedActionId(newId); 1532 }; 1533 const removeBodyAction = (id) => { 1534 pushUndo(); 1535 setGlobalActions((prev) => prev.filter((a) => a.id !== id)); 1536 if (selectedActionId === id) setSelectedActionId(null); 1537 }; 1538 const cloneBodyAction = (id) => { 1539 pushUndo(); 1540 const src = globalActions.find((a) => a.id === id); 1541 if (!src) return; 1542 const newId = `${src.id}_copy`; 1543 setGlobalActions((prev) => [...prev, { ...JSON.parse(JSON.stringify(src)), id: newId, name: src.name + " Copy" }]); 1544 setSelectedActionId(newId); 1545 }; 1546 const updateBodyAction = (id, updates) => setGlobalActions((prev) => prev.map((a) => a.id === id ? { ...a, ...updates } : a)); 1547 const addBodyChannel = (actionId) => { 1548 const ch = { joint: "body", property: "y", type: "wave", waveform: "sine", frequency: 1, amplitude: 10, amplitudeBase: 0, intensityScale: 0, phase: 0, offset: 0 }; 1549 setGlobalActions((prev) => prev.map((a) => a.id === actionId ? { ...a, channels: [...a.channels, ch] } : a)); 1550 }; 1551 const removeBodyChannel = (actionId, idx) => { 1552 setGlobalActions((prev) => prev.map((a) => a.id === actionId ? { ...a, channels: a.channels.filter((_, i) => i !== idx) } : a)); 1553 if (selectedChannelIdx === idx) setSelectedChannelIdx(null); 1554 }; 1555 const updateBodyChannel = (actionId, idx, updates) => setGlobalActions((prev) => prev.map((a) => a.id === actionId ? { ...a, channels: a.channels.map((c, i) => i === idx ? { ...c, ...updates } : c) } : a)); 1556 const addBodyKeyframe = (actionId, channelIdx) => { 1557 var _a2; 1558 const action = globalActions.find((a) => a.id === actionId); 1559 const channel = action == null ? void 0 : action.channels[channelIdx]; 1560 if (!channel) return; 1561 const times = ((_a2 = channel.keyframes) == null ? void 0 : _a2.map((k) => k.time)) || []; 1562 const t = times.length > 0 ? Math.min(1, (times[times.length - 1] || 0) + 0.1) : 0.5; 1563 const nkf = [...channel.keyframes || [], { time: Math.round(t * 100) / 100, value: 0, easing: "sine" }].sort((a, b) => a.time - b.time); 1564 updateBodyChannel(actionId, channelIdx, { keyframes: nkf }); 1565 }; 1566 const addBodyRule = () => { 1567 const r = { id: `rule_${Date.now()}`, name: "New Rule", actionIds: [], enabled: true, condition: { scope: "all" } }; 1568 setGlobalActionRules((prev) => [...prev, r]); 1569 setSelectedActionRuleId(r.id); 1570 }; 1571 const updateBodyRule = (id, updates) => setGlobalActionRules((prev) => prev.map((r) => r.id === id ? { ...r, ...updates } : r)); 1572 const removeBodyRule = (id) => { 1573 setGlobalActionRules((prev) => prev.filter((r) => r.id !== id)); 1574 if (selectedActionRuleId === id) setSelectedActionRuleId(null); 1575 }; 1576 const saveActionsToConfigs = () => { 1577 setConfigs((prev) => { 1578 const next = { ...prev }; 1579 Object.keys(next).forEach((cid) => { 1580 next[cid] = { ...next[cid], actions: globalActions, actionRules: globalActionRules }; 1581 }); 1582 return next; 1583 }); 1584 }; 1585 const bg = currentConfigToEdit || defaultBackgroundConfig; 1586 let bgStyle = {}; 1587 if (bg.type === "color") { 1588 bgStyle = { backgroundColor: bg.color }; 1589 } else if (bg.type === "gradient") { 1590 const { type, angle, stops } = bg.gradient; 1591 const stopStr = stops.slice().sort((a, b) => a.offset - b.offset).map((s) => `${s.color} ${s.offset}%`).join(", "); 1592 if (type === "radial") { 1593 bgStyle = { background: `radial-gradient(circle, ${stopStr})` }; 1594 } else { 1595 bgStyle = { background: `linear-gradient(${angle}deg, ${stopStr})` }; 1596 } 1597 } else if (bg.type === "image") { 1598 bgStyle = { backgroundColor: bg.color || "#050505" }; 1599 } 1600 const imageStyle = bg.type === "image" ? { 1601 backgroundImage: `url("${bg.image.url}")`, 1602 backgroundSize: bg.image.size, 1603 backgroundPosition: bg.image.position, 1604 backgroundRepeat: bg.image.repeat, 1605 filter: bg.image.blur ? `blur(${bg.image.blur}px)` : void 0, 1606 transform: bg.image.blur ? "scale(1.05)" : void 0 1607 } : {}; 1608 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col h-full bg-[#0a0a0a] text-zinc-200", children: [
1609 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between p-4 border-b border-zinc-800 bg-[#111]", children: [ 1610 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1611 /* @__PURE__ */ jsxRuntimeExports.jsxs("h1", { className: "text-xl font-bold text-white flex items-center gap-2", children: [ 1612 /* @__PURE__ */ jsxRuntimeExports.jsx(PenLine, { size: 20, className: "text-indigo-400" }), 1613 " Puppet Studio" 1614 ] }), 1615 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-zinc-400", children: "Configure SVGs and IK Mappings for Real-Time Stage Performance" }) 1616 ] }), 1617 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [ 1618 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: loadConfigs, className: "p-2 rounded bg-zinc-800 hover:bg-zinc-700 text-zinc-300", title: "Reload from server", children: /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { size: 18 }) }), 1619 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => { 1620 if (confirm("Reset to defaults? This will erase all your custom puppets AND instruments!")) { 1621 seedDefaults(); 1622 seedInstDefaults(); 1623 seedClothingDefaults(); 1624 } 1625 }, className: "px-4 py-2 bg-red-600/80 hover:bg-red-500 text-white rounded font-bold", children: "Reset to Defaults" }),
1626 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1627 "button", 1628 { 1629 onClick: handleSave, 1630 disabled: saveStatus === "saving", 1631 className: `flex items-center gap-2 px-4 py-2 text-white rounded font-bold transition-all duration-200 disabled:opacity-50 ${saveStatus === "saved" ? "bg-emerald-600 hover:bg-emerald-500" : saveStatus === "failed" ? "bg-rose-600 hover:bg-rose-500" : "bg-indigo-600 hover:bg-indigo-500"}`, 1632 children: [ 1633 /* @__PURE__ */ jsxRuntimeExports.jsx(Save, { size: 18 }), 1634 " ", 1635 saveStatus === "saving" ? "Saving..." : saveStatus === "saved" ? "Saved" : saveStatus === "failed" ? "Failed" : "Save All" 1636 ] 1637 } 1638 ) 1639 ] }) 1640 ] }), 1641 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-4 px-4 bg-[#111] border-b border-zinc-800", children: [ 1642 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setActiveTab("characters"), className: `px-4 py-3 text-sm font-bold border-b-2 ${activeTab === "characters" ? "border-indigo-500 text-indigo-400" : "border-transparent text-zinc-500 hover:text-zinc-300"}`, children: "Characters" }),
1643 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setActiveTab("instruments"), className: `px-4 py-3 text-sm font-bold border-b-2 ${activeTab === "instruments" ? "border-indigo-500 text-indigo-400" : "border-transparent text-zinc-500 hover:text-zinc-300"}`, children: "Instruments" }), 1644 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setActiveTab("clothing"), className: `px-4 py-3 text-sm font-bold border-b-2 ${activeTab === "clothing" ? "border-indigo-500 text-indigo-400" : "border-transparent text-zinc-500 hover:text-zinc-300"}`, children: "Clothing" }), 1645 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => { 1646 setActiveTab("actions"); 1647 setShowActionPaths(true); 1648 }, className: `px-4 py-3 text-sm font-bold border-b-2 ${activeTab === "actions" ? "border-indigo-500 text-indigo-400" : "border-transparent text-zinc-500 hover:text-zinc-300"}`, children: "Actions" }), 1649 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setActiveTab("rules"), className: `px-4 py-3 text-sm font-bold border-b-2 ${activeTab === "rules" ? "border-indigo-500 text-indigo-400" : "border-transparent text-zinc-500 hover:text-zinc-300"}`, children: "Rigging Rules" }),
1650 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setActiveTab("background"), className: `px-4 py-3 text-sm font-bold border-b-2 ${activeTab === "background" ? "border-indigo-500 text-indigo-400" : "border-transparent text-zinc-500 hover:text-zinc-300"}`, children: "Background" }) 1651 ] }), 1652 activeTab === "rules" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto p-8 bg-[#0a0a0a] text-zinc-300", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "max-w-4xl mx-auto space-y-8", children: [ 1653 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1654 /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-2xl font-bold text-white mb-4", children: "Core Rigging Rules" }), 1655 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-zinc-400", children: "To make sure your parts work perfectly in the Puppet Engine system, follow these core rules when designing your SVG or PNG assets." }) 1656 ] }), 1657 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-[#111] border border-zinc-800 p-6 rounded-lg space-y-4", children: [
1658 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-lg font-bold text-indigo-400", children: "1. Naming & Organization (The SVG wrapper)" }), 1659 /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { children: [ 1660 "You will need to wrap your PNGs in an SVG file and assign specific ", 1661 /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-emerald-400 bg-zinc-900 px-1 rounded", children: "id" }), 1662 " attributes to groups. The system looks for these EXACT IDs to know what to animate:" 1663 ] }), 1664 /* @__PURE__ */ jsxRuntimeExports.jsxs("ul", { className: "list-disc ml-6 space-y-1 text-sm font-mono text-zinc-400", children: [ 1665 /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: 'id="head"' }), 1666 /* @__PURE__ */ jsxRuntimeExports.jsx("li", { children: 'id="body"' }), 1667 /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [ 1668 'id="upper_arm_l" ', 1669 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-500 font-sans", children: "(Left upper arm)" }) 1670 ] }),
1671 /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [ 1672 'id="lower_arm_l" ', 1673 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-500 font-sans", children: "(Left lower arm)" }) 1674 ] }), 1675 /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [ 1676 'id="hand_l" ', 1677 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-500 font-sans", children: "(Left hand)" }) 1678 ] }), 1679 /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [ 1680 'id="upper_arm_r" ', 1681 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-500 font-sans", children: "(Right upper arm)" }) 1682 ] }), 1683 /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [ 1684 'id="lower_arm_r" ', 1685 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-500 font-sans", children: "(Right lower arm)" }) 1686 ] }), 1687 /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [ 1688 'id="hand_r" ',
1689 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-500 font-sans", children: "(Right hand)" }) 1690 ] }), 1691 /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [ 1692 'id="leg_l" ', 1693 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-500 font-sans", children: "(Left leg)" }) 1694 ] }), 1695 /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [ 1696 'id="foot_l" ', 1697 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-500 font-sans", children: "(Left foot)" }) 1698 ] }), 1699 /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [ 1700 'id="leg_r" ', 1701 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-500 font-sans", children: "(Right leg)" }) 1702 ] }), 1703 /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [ 1704 'id="foot_r" ', 1705 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-500 font-sans", children: "(Right foot)" }) 1706 ] }) 1707 ] }),
1708 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs bg-zinc-900 border border-zinc-800 p-2 rounded text-zinc-500", children: "Note: For the arms, left (_l) and right (_r) are from the character's perspective." }) 1709 ] }), 1710 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-[#111] border border-zinc-800 p-6 rounded-lg space-y-4", children: [ 1711 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-lg font-bold text-indigo-400", children: "2. Orientation & Default Poses" }), 1712 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "For the mathematical inverse kinematics (IK) engine to calculate joints correctly:" }), 1713 /* @__PURE__ */ jsxRuntimeExports.jsxs("ul", { className: "list-disc ml-6 space-y-3", children: [ 1714 /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [ 1715 /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Body & Head:" }), 1716 " Should be drawn upright facing forward." 1717 ] }), 1718 /* @__P
1718URE__ */ jsxRuntimeExports.jsxs("li", { children: [ 1719 /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Legs:" }), 1720 " Should be drawn vertically (pointing down)." 1721 ] }), 1722 /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [ 1723 /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Arms (Crucial):" }), 1724 " All arm parts (upper_arm, lower_arm, hand) MUST be drawn perfectly horizontal, pointing to the right, as if the character is T-posing but with only their right side outstretched.", 1725 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2 pl-4 border-l-2 border-indigo-500 text-sm text-zinc-400 space-y-2", children: [ 1726 /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { children: [ 1727 /* @__PURE__ */ jsxRuntimeExports.jsx("em", { children: "Why?" }), 1728 " The engine assumes a 0-degree rotation is pointing right. If you draw the arm pointing down, the engine will mathematically bend it wrong when playing an instrument." 1729 ] }),
1730 /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { children: [ 1731 /* @__PURE__ */ jsxRuntimeExports.jsx("em", { children: "Wait, even the left arm?" }), 1732 " Yes! Draw the left arm pieces pointing right as well. The engine will flip/rotate them to the correct position when attaching them to the body." 1733 ] }) 1734 ] }) 1735 ] }) 1736 ] }) 1737 ] }), 1738 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-[#111] border border-zinc-800 p-6 rounded-lg space-y-4", children: [ 1739 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-lg font-bold text-indigo-400", children: "3. Origins and Pivot Points (Sockets)" }), 1740 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "Because you are wrapping PNGs, you don't get natural center-points from vector shapes. The system will try to pivot items based on their bounding boxes (e.g., lower arm pivots at the edge of the upper arm)." }), 1741 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-indigo-950/20 border border-indigo-500/20 p-4 rounded text-sm text-indigo-200", children: [
1742 /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Pro-tip:" }), 1743 " In your SVG, you can define invisible circles with specific IDs to tell the engine EXACTLY where joints should connect:", 1744 /* @__PURE__ */ jsxRuntimeExports.jsxs("ul", { className: "list-none mt-2 space-y-1 font-mono text-xs", children: [ 1745 /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [ 1746 '<circle id="socket_shoulder_l" cx="..." cy="..." r="1" opacity="0" /> ', 1747 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-sans text-zinc-400", children: "(Where the left arm attaches to the body)" }) 1748 ] }), 1749 /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [ 1750 '<circle id="socket_mouth" cx="..." cy="..." r="1" opacity="0" /> ', 1751 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-sans text-zinc-400", children: "(Where instruments like flutes attach)" }) 1752 ] }) 1753 ] }) 1754 ] }) 1755 ] }),
1756 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-[#111] border border-zinc-800 p-6 rounded-lg space-y-4", children: [ 1757 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-lg font-bold text-indigo-400", children: "4. Canvas Size" }), 1758 /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { children: [ 1759 "A square canvas is best (e.g., ", 1760 /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-emerald-400 bg-zinc-900 px-1 rounded", children: "800x800" }), 1761 " or ", 1762 /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-emerald-400 bg-zinc-900 px-1 rounded", children: "1000x1000" }), 1763 "). Make sure your parts are scaled consistently relative to each other." 1764 ] }) 1765 ] }) 1766 ] }) }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-1 overflow-hidden", children: [ 1767 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-64 resize-x overflow-y-auto overflow-x-hidden min-w-[200px] max-w-[500px] border-r border-zinc-800 bg-[#0d0d0d] flex flex-col shrink-0 custom-scrollbar", children: [ 1768 activeTab === "characters" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1769 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-3 border-b border-zinc-800 flex flex-col gap-2", children: [ 1770 /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { onClick: handleAdd, className: "w-full flex items-center justify-center gap-2 px-3 py-2 bg-zinc-800 hover:bg-zinc-700 text-white rounded text-sm", children: [ 1771 /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { size: 16 }), 1772 " Add Puppet" 1773 ] }), 1774 /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { onClick: () => setShowRiggingBuilder(true), className: "w-full flex items-center justify-center gap-2 px-3 py-2 bg-indigo-600/20 hover:bg-indigo-600/30 border border-indigo-500/30 text-indigo-300 hover:text-indigo-200 rounded text-sm font-bold transition-colors", children: [ 1775 /* @__PURE__ */ jsxRuntimeExports.jsx(Image$1, { size: 16 }), 1776 " PNG Asset Builder" 1777 ] }) 1778 ] }), 1779 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto p-2 space-y-1 custom-scrollbar", children: Object.values(configs).map((c) => /* @__PURE__ */ jsxRuntimeExports.jsxs( 1780 "div", 1781 { 1782 onClick: () => setSelectedId(c.id), 1783 className: `flex items-center justify-between p-3 rounded cursor-pointer transition-colors ${selectedId === c.id ? "bg-indigo-600/20 text-indigo-300 border border-indigo-600/30" : "hover:bg-zinc-800 text-zinc-400 border border-transparent"}`, 1784 children: [
1785 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "truncate", children: [ 1786 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-bold text-sm truncate", children: c.name }), 1787 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs opacity-60 uppercase tracking-wider mt-0.5", children: c.family }) 1788 ] }), 1789 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: (e) => { 1790 e.stopPropagation(); 1791 handleDelete(c.id); 1792 }, className: "text-zinc-600 hover:text-red-400", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 14 }) }) 1793 ] 1794 }, 1795 c.id 1796 )) }) 1797 ] }), 1798 activeTab === "clothing" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ 1799 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-3 border-b border-zinc-800", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { onClick: () => { 1800 const id = `new_clothing_${Date.now()}`; 1801 setClothingConfigs((prev) => ({ ...prev, [id]: { id, parts: {} } })); 1802 setSelectedClothingId(id); 1803 }, className: "w-full flex items-center justify-center gap-2 px-3 py-2 bg-zinc-800 hover:bg-zinc-700 text-white rounded text-sm", children: [
1804 /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { size: 16 }), 1805 " Add Clothing" 1806 ] }) }), 1807 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto p-2 space-y-1 custom-scrollbar", children: Object.values(clothingConfigs).map((c) => /* @__PURE__ */ jsxRuntimeExports.jsxs( 1808 "div", 1809 { 1810 onClick: () => setSelectedClothingId(c.id), 1811 className: `flex items-center justify-between p-3 rounded cursor-pointer transition-colors ${selectedClothingId === c.id ? "bg-indigo-600/20 text-indigo-300 border border-indigo-600/30" : "hover:bg-zinc-800 text-zinc-400 border border-transparent"}`, 1812 children: [ 1813 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "truncate", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-bold text-sm truncate", children: c.id }) }), 1814 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: (e) => { 1815 e.stopPropagation(); 1816 if (!confirm("Are you sure?")) return; 1817 const newConfigs = { ...clothingConfigs }; 1818 delete newConfigs[c.id]; 1819 setClothingConfigs(newConfigs); 1820 if (selectedClothingId === c.id) setSelectedClothingId(Object.keys(newConfigs)[0] || null); 1821 }, className: "text-zinc-600 hover:text-red-400", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 14 }) }) 1822 ] 1823 }, 1824 c.id 1825 )) }) 1826 ] }), 1827 activeTab === "instruments" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto p-2 space-y-1 custom-scrollbar", children: [
1828 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs font-bold text-zinc-500 uppercase tracking-wider p-2", children: "Melodic (1-128)" }), 1829 Object.keys(instrumentConfigs.melodic).map((k) => /* @__PURE__ */ jsxRuntimeExports.jsx( 1830 "div", 1831 { 1832 onClick: () => setSelectedInstKey(`m_${k}`), 1833 className: `p-2 text-sm rounded cursor-pointer truncate ${selectedInstKey === `m_${k}` ? "bg-indigo-600/20 text-indigo-300" : "hover:bg-zinc-800 text-zinc-400"}`, 1834 children: getInstrumentLabel("melodic", k) 1835 }, 1836 `m_${k}` 1837 )), 1838 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs font-bold text-zinc-500 uppercase tracking-wider p-2 mt-4", children: "Percussion (Ch 10)" }), 1839 Object.keys(instrumentConfigs.percussion).map((k) => /* @__PURE__ */ jsxRuntimeExports.jsx( 1840 "div", 1841 { 1842 onClick: () => setSelectedInstKey(`p_${k}`), 1843 className: `p-2 text-sm rounded cursor-pointer truncate ${selectedInstKey === `p_${k}` ? "bg-indigo-600/20 text-indigo-300" : "hover:bg-zinc-800 text-zinc-400"}`, 1844 children: getInstrumentLabel("percussion", k) 1845 }, 1846 `p_${k}` 1847 )) 1848 ] }), 1849 activeTab === "actions" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1850 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b border-zinc-800 bg-[#111] shrink-0", children: [ 1851 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-bold text-zinc-400 uppercase tracking-widest text-indigo-400", children: "Body Actions" }), 1852 /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { onClick: addBodyAction, className: "text-indigo-400 hover:text-indigo-300 text-xs flex items-center gap-1", children: [ 1853 /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { size: 12 }), 1854 " Add" 1855 ] }) 1856 ] }), 1857 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "overflow-y-auto shrink-0 border-b border-zinc-800", style: { maxHeight: "50%" }, children: globalActions.filter((a) => a.category === "body").map((action) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { onClick: () => { 1858 setSelectedActionId(action.id); 1859 setSelectedActionRuleId(null); 1860 setSelectedChannelIdx(null); 1861 }, className: `flex items-center justify-between px-4 py-2.5 cursor-pointer border-b border-zinc-900 hover:bg-zinc-800 group ${selectedActionId === action.id ? "bg-indigo-900/30 border-l-2 border-l-indigo-500" : ""}`, children: [
1862 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1863 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm font-medium text-zinc-200", children: action.name }), 1864 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs text-zinc-500", children: [ 1865 TRIGGER_LABELS_MAP[action.trigger], 1866 " · ", 1867 action.channels.length, 1868 " ch · p", 1869 action.priority 1870 ] }) 1871 ] }), 1872 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1 opacity-0 group-hover:opacity-100", children: [ 1873 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: (e) => { 1874 e.stopPropagation(); 1875 cloneBodyAction(action.id); 1876 }, className: "p-1 text-zinc-500 hover:text-zinc-200", title: "Clone", children: /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { size: 12 }) }), 1877 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: (e) => { 1878 e.stopPropagation(); 1879 removeBodyAction(action.id); 1880 }, className: "p-1 text-zinc-500 hover:text-red-400", title: "Delete", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 12 }) }) 1881 ] }) 1882 ] }, action.id)) }),
1883 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b border-zinc-800 bg-[#111] shrink-0", children: [ 1884 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-bold text-zinc-400 uppercase tracking-widest text-indigo-400", children: "Action Rules" }), 1885 /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { onClick: addBodyRule, className: "text-indigo-400 hover:text-indigo-300 text-xs flex items-center gap-1", children: [ 1886 /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { size: 12 }), 1887 " Add" 1888 ] }) 1889 ] }), 1890 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "overflow-y-auto flex-1", children: globalActionRules.map((rule) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { onClick: () => { 1891 setSelectedActionRuleId(rule.id); 1892 setSelectedActionId(null); 1893 }, className: `flex items-center justify-between px-4 py-2.5 cursor-pointer border-b border-zinc-900 hover:bg-zinc-800 group ${selectedActionRuleId === rule.id ? "bg-indigo-900/30 border-l-2 border-l-indigo-500" : ""}`, children: [
1894 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1895 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 1896 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `w-1.5 h-1.5 rounded-full ${rule.enabled ? "bg-green-400" : "bg-zinc-600"}` }), 1897 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm text-zinc-200", children: rule.name }) 1898 ] }), 1899 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs text-zinc-500 ml-3.5", children: [ 1900 rule.condition.scope === "all" ? "All characters" : rule.condition.scope === "family" ? `Families: ${(rule.condition.families || []).join(", ")}` : `Selected: ${(rule.condition.characterIds || []).join(", ")}`, 1901 " · ", 1902 rule.actionIds.length, 1903 " action", 1904 rule.actionIds.length !== 1 ? "s" : "" 1905 ] }) 1906 ] }), 1907 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: (e) => { 1908 e.stopPropagation(); 1909 removeBodyRule(rule.id); 1910 }, className: "p-1 text-zinc-500 hover:text-red-400 opacity-0 group-hover:opacity-100", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 12 }) }) 1911 ] }, rule.id)) }),
1912 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-3 border-t border-zinc-800 bg-[#111] shrink-0", children: [ 1913 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => { 1914 saveActionsToConfigs(); 1915 handleSave(); 1916 }, className: "w-full py-2 bg-indigo-600 hover:bg-indigo-500 text-white text-sm font-bold rounded", children: "Save Actions" }), 1917 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-600 text-center mt-1", children: "Applies to all characters" }) 1918 ] }) 1919 ] }), 1920 activeTab === "background" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ 1921 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-between px-4 py-3 border-b border-zinc-800 bg-[#111] shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-bold text-zinc-400 uppercase tracking-widest text-indigo-400", children: "Backgrounds" }) }),
1922 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto p-2 space-y-1 custom-scrollbar", children: [ 1923 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1924 "div", 1925 { 1926 onClick: () => { 1927 setSelectedPresetId(null); 1928 }, 1929 className: `p-3 rounded cursor-pointer border hover:bg-zinc-800 transition-colors ${selectedPresetId === null ? "bg-indigo-600/20 text-indigo-300 border-indigo-600/30" : "text-zinc-400 border-transparent"}`, 1930 children: [ 1931 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-bold text-sm", children: "Global Default Stage" }), 1932 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs opacity-60 mt-0.5", children: "Applies when no tags match" }) 1933 ] 1934 } 1935 ), 1936 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "border-t border-zinc-800 my-2" }), 1937 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-bold text-zinc-500 uppercase tracking-widest px-3 block mb-1", children: "Tag-Based Presets" }), 1938 backgroundPresets.map((preset) => /* @__PURE__ */ jsxRuntimeExports.jsxs( 1939 "div", 1940 { 1941 onClick: () => { 1942 setSelectedPresetId(preset.id); 1943 }, 1944 className: `p-3 rounded cursor-pointer border hover:bg-zinc-800 transition-colors ${selectedPresetId === preset.id ? "bg-indigo-600/20 text-indigo-300 border-indigo-600/30" : "text-zinc-400 border-transparent"}`, 1945 children: [
1946 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-bold text-sm", children: preset.name }), 1947 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs opacity-60 capitalize mt-0.5", children: [ 1948 preset.config.type, 1949 " · tags: ", 1950 preset.tags.length > 0 ? preset.tags.join(", ") : "none" 1951 ] }) 1952 ] 1953 }, 1954 preset.id 1955 )) 1956 ] }) 1957 ] }) 1958 ] }), 1959 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 bg-[#0a0a0a] flex flex-col relative overflow-hidden", children: activePreviewConfig ? /* @__PURE__ */ jsxRuntimeExports.jsxs( 1960 "div", 1961 { 1962 ref: containerRef, 1963 className: "flex-1 flex items-center justify-center p-4 relative overflow-hidden focus:outline-none", 1964 style: bgStyle, 1965 tabIndex: 0, 1966 onMouseDown: (e) => { 1967 if (document.activeElement instanceof HTMLElement) { 1968 document.activeElement.blur(); 1969 } 1970 if (e.target === e.currentTarget || e.target.classList.contains("bg-black/80")) { 1971 e.preventDefault(); 1972 setDraggingPoint({ 1973 target: "pan", 1974 startX: viewPanX, 1975 startY: viewPanY, 1976 mouseStartX: e.clientX, 1977 mouseStartY: e.clientY 1978 }); 1979 } 1980 }, 1981 onClick: (e) => { 1982 if (editMode === "none" || !activePreviewConfig) return; 1983 pushUndo(); 1984 const svg = previewSvgRef.current || e.currentTarget.querySelector("svg"); 1985 if (!svg) return; 1986 const pt = svg.createSVGPoint(); 1987 pt.x = e.clientX; 1988 pt.y = e.clientY; 1989 const svgP = pt.matrixTransform(svg.getScreenCTM().inverse()); 1990 const charScale = activePreviewConfig.scale ?? 1; 1991 const anchorX = activePreviewConfig.anchorX ?? 0; 1992 const anchorY = activePreviewConfig.anchorY ?? 0; 1993 const innerX = Math.round(svgP.x / charScale - anchorX); 1994 const innerY = Math.round(svgP.y / charScale - anchorY); 1995 const anchorModeX = Math.round(svgP.x / charScale); 1996 const anchorModeY = Math.round(svgP.y / charScale); 1997 if (activeTab === "characters" && !previewInstKey) { 1998 if (editMode === "anchor") { 1999 handleConfigChange(activePreviewConfig.id, { anchorX: anchorModeX, anchorY: anchorModeY }); 2000 } else if (editMode === "shoulder_l") { 2001 handleIkChange(activePreviewConfig.id, "ikLeftArm", "originX", innerX); 2002 handleIkChange(activePreviewConfig.id, "ikLeftArm", "originY", innerY); 2003 } else if (editMode === "shoulder_r") { 2004 handleIkChange(activePreviewConfig.id, "ikRightArm", "originX", innerX); 2005 handleIkChange(activePreviewConfig.id, "ikRightArm", "originY", innerY); 2006 } else if (editMode.startsWith("socket_")) { 2007 const socketName = editMode.replace("socket_", ""); 2008 const currentSockets = activePreviewConfig.sockets || {}; 2009 handleConfigChange(activePreviewConfig.id, { 2010 sockets: { 2011 ...currentSockets, 2012 [socketName]: { x: innerX, y: innerY } 2013 } 2014 }); 2015 } 2016 } else if (activeTab === "instruments" || activeTab === "characters" && previewInstKey) { 2017 if (editMode.startsWith("plug_")) { 2018 const plugName = editMode.replace("plug_", ""); 2019 const targetCoords = getLocalSpacePt(".custom-instrument-svg") || { x: localX, y: localY }; 2020 setInstrumentConfigs((prev) => { 2021 const typeObj = prev[editingInstType]; 2022 const instObj = typeObj[editingInstIndex] || {}; 2023 const currentPlugs = instObj.plugs || {}; 2024 const updated = { 2025 ...instObj, 2026 plugs: { 2027 ...currentPlugs, 2028 [plugName]: { ...currentPlugs[plugName] || {}, x: targetCoords.x, y: targetCoords.y } 2029 } 2030 }; 2031 return { 2032 ...prev, 2033 [editingInstType]: { 2034 ...typeObj, 2035 [editingInstIndex]: updated 2036 } 2037 }; 2038 }); 2039 } else { 2040 const updateMotion = (hand, point, lX, lY) => { 2041 const finalX = localX; 2042 const finalY = localY; 2043 setInstrumentConfigs((prev) => { 2044 const typeObj = prev[editingInstType]; 2045 const instObj = typeObj[editingInstIndex] || {}; 2046 const motionObj = instObj[hand] || { logic: "none", start: { x: 0, y: 0 }, end: { x: 0, y: 0 } }; 2047 let newRestPose = instObj.restPose || {}; 2048 if (point === "start") { 2049 newRestPose = { 2050 ...newRestPose, 2051 [hand === "leftHandMotion" ? "leftHand" : "rightHand"]: { x: finalX, y: finalY } 2052 }; 2053 } 2054 return { 2055 ...prev, 2056 [editingInstType]: { 2057 ...typeObj, 2058 [editingInstIndex]: { 2059 ...instObj, 2060 [hand]: { ...motionObj, [point]: { x: finalX, y: finalY } },
2061 restPose: newRestPose 2062 } 2063 } 2064 }; 2065 }); 2066 }; 2067 if (editMode === "path_l_start") updateMotion("leftHandMotion", "start"); 2068 else if (editMode === "path_l_end") updateMotion("leftHandMotion", "end"); 2069 else if (editMode === "path_r_start") updateMotion("rightHandMotion", "start"); 2070 else if (editMode === "path_r_end") updateMotion("rightHandMotion", "end"); 2071 else if (editMode === "head_anchor") { 2072 setInstrumentConfigs((prev) => { 2073 const typeObj = prev[editingInstType]; 2074 const instObj = typeObj[editingInstIndex] || {}; 2075 return { 2076 ...prev, 2077 [editingInstType]: { 2078 ...typeObj, 2079 [editingInstIndex]: { 2080 ...instObj, 2081 headAnchor: { x: localX, y: localY } 2082 } 2083 } 2084 }; 2085 }); 2086 } 2087 } 2088 } else if (activeTab === "clothing" && selectedClothingDef) { 2089 if (editMode.startsWith("clothing_plug_")) { 2090 const parts = editMode.split("_"); 2091 const pointKey = parts[2]; 2092 const partName = parts.slice(3).join("_"); 2093 const targetCoords = getLocalSpacePt(`.custom-clothing-mount[data-target-joint="${partName}"] .custom-clothing-offset`) || { x: localX, y: localY }; 2094 setClothingConfigs((prev) => { 2095 const clothObj = prev[selectedClothingDef.id]; 2096 if (!clothObj) return prev; 2097 const currentPlugs = clothObj.plugs || {}; 2098 const partPlugs = currentPlugs[partName] || {}; 2099 return { 2100 ...prev, 2101 [selectedClothingDef.id]: { 2102 ...clothObj, 2103 plugs: { 2104 ...currentPlugs, 2105 [partName]: { 2106 ...partPlugs, 2107 [pointKey]: { x: targetCoords.x, y: targetCoords.y } 2108 } 2109 } 2110 } 2111 }; 2112 }); 2113 } 2114 } 2115 }, 2116 onMouseMove: (e) => { 2117 var _a2, _b2, _c2; 2118 if (!draggingPoint || !activePreviewConfig) return; 2119 if (draggingPoint.target === "pan") { 2120 const deltaX = e.clientX - draggingPoint.mouseStartX; 2121 const deltaY = e.clientY - draggingPoint.mouseStartY; 2122 setViewPanX(draggingPoint.startX - deltaX / viewZoom); 2123 setViewPanY(draggingPoint.startY - deltaY / viewZoom); 2124 return; 2125 } 2126 const svg = previewSvgRef.current || e.currentTarget.querySelector("svg"); 2127 if (!svg) return; 2128 const pt = svg.createSVGPoint(); 2129 pt.x = e.clientX; 2130 pt.y = e.clientY; 2131 const svgP = pt.matrixTransform((_a2 = svg.getScreenCTM()) == null ? void 0 : _a2.inverse()); 2132 if (draggingPoint.target === "socket" && activePreviewConfig) { 2133 const socketName = draggingPoint.jointId; 2134 const charScale = activePreviewConfig.scale || 1; 2135 const anchorX = activePreviewConfig.anchorX ?? 0; 2136 const anchorY = activePreviewConfig.anchorY ?? 0; 2137 const newX = Math.round(svgP.x / charScale - anchorX); 2138 const newY = Math.round(svgP.y / charScale - anchorY); 2139 const currentSockets = activePreviewConfig.sockets || {}; 2140 handleConfigChange(activePreviewConfig.id, { 2141 sockets: { 2142 ...currentSockets, 2143 [socketName]: { x: newX, y: newY } 2144 } 2145 }); 2146 return; 2147 } 2148 if (draggingPoint.target === "plug") { 2149 const targetEl = svg.querySelector(".custom-instrument-svg"); 2150 if (targetEl) { 2151 const activeEl = targetEl.querySelector("svg") || targetEl.firstElementChild || targetEl; 2152 const ctm = activeEl.getScreenCTM(); 2153 if (ctm) { 2154 const ptLocal = svg.createSVGPoint();
2155 ptLocal.x = e.clientX; 2156 ptLocal.y = e.clientY; 2157 const localP = ptLocal.matrixTransform(ctm.inverse()); 2158 const newX = Math.round(localP.x); 2159 const newY = Math.round(localP.y); 2160 const plugName = draggingPoint.jointId; 2161 setInstrumentConfigs((prev) => { 2162 const typeObj = prev[editingInstType]; 2163 const instObj = typeObj[editingInstIndex] || {}; 2164 const currentPlugs = instObj.plugs || {}; 2165 const updated = { 2166 ...instObj, 2167 plugs: { 2168 ...currentPlugs, 2169 [plugName]: { ...currentPlugs[plugName] || {}, x: newX, y: newY } 2170 } 2171 }; 2172 return { 2173 ...prev, 2174 [editingInstType]: { 2175 ...typeObj, 2176 [editingInstIndex]: updated 2177 } 2178 }; 2179 }); 2180 } 2181 } 2182 return; 2183 } 2184 if (draggingPoint.target === "clothing_plug" && selectedClothingDef) { 2185 const partName = draggingPoint.plugPart; 2186 const pointKey = draggingPoint.plugKey; 2187 const targetEl = svg.querySelector(`.custom-clothing-mount[data-target-joint="${partName}"] .custom-clothing-offset`); 2188 if (targetEl) { 2189 const activeEl = targetEl.querySelector("svg") || targetEl.firstElementChild || targetEl; 2190 const ctm = activeEl.getScreenCTM(); 2191 if (ctm) { 2192 const ptLocal = svg.createSVGPoint(); 2193 ptLocal.x = e.clientX; 2194 ptLocal.y = e.clientY; 2195 const localP = ptLocal.matrixTransform(ctm.inverse()); 2196 const newX = Math.round(localP.x); 2197 const newY = Math.round(localP.y); 2198 setClothingConfigs((prev) => { 2199 const clothObj = prev[selectedClothingDef.id]; 2200 if (!clothObj) return prev; 2201 const currentPlugs = clothObj.plugs || {}; 2202 const partPlugs = currentPlugs[partName] || {}; 2203 return { 2204 ...prev, 2205 [selectedClothingDef.id]: { 2206 ...clothObj, 2207 plugs: { 2208 ...currentPlugs, 2209 [partName]: { 2210 ...partPlugs, 2211 [pointKey]: { x: newX, y: newY } 2212 } 2213 } 2214 } 2215 }; 2216 }); 2217 } 2218 } 2219 return; 2220 } 2221 if (draggingPoint.target === "clothing" && selectedClothingDef && draggingPoint.inverseMatrix) { 2222 const jointId = draggingPoint.jointId; 2223 const ptLocal = svg.createSVGPoint(); 2224 ptLocal.x = e.clientX; 2225 ptLocal.y = e.clientY; 2226 const localP = ptLocal.matrixTransform(draggingPoint.inverseMatrix); 2227 const localDeltaX = localP.x - draggingPoint.startLocalX; 2228 const localDeltaY = localP.y - draggingPoint.startLocalY; 2229 const newX = Math.round(draggingPoint.startX + localDeltaX); 2230 const newY = Math.round(draggingPoint.startY + localDeltaY); 2231 const currentTransform = ((_b2 = selectedClothingDef.transforms) == null ? void 0 : _b2[jointId]) || { x: 0, y: 0, scaleX: 1, scaleY: 1, rotation: 0 }; 2232 setClothingConfigs((prev) => ({ 2233 ...prev, 2234 [selectedClothingDef.id]: { 2235 ...selectedClothingDef, 2236 transforms: { 2237 ...selectedClothingDef.transforms, 2238 [jointId]: { 2239 ...currentTransform, 2240 x: newX, 2241 y: newY 2242 } 2243 } 2244 } 2245 })); 2246 return; 2247 } 2248 if ((draggingPoint.target === "instrument" || draggingPoint.target === "accessory") && selectedInstKey && draggingPoint.inverseMatrix) { 2249 const isAccessory = draggingPoint.target === "accessory"; 2250 const ptLocal = svg.createSVGPoint();
2251 ptLocal.x = e.clientX; 2252 ptLocal.y = e.clientY; 2253 const localP = ptLocal.matrixTransform(draggingPoint.inverseMatrix); 2254 const localDeltaX = localP.x - draggingPoint.startLocalX; 2255 const localDeltaY = localP.y - draggingPoint.startLocalY; 2256 const newX = Math.round(draggingPoint.startX + localDeltaX); 2257 const newY = Math.round(draggingPoint.startY + localDeltaY); 2258 setInstrumentConfigs((prev) => { 2259 const typeObj = prev[editingInstType]; 2260 const instObj = typeObj[editingInstIndex]; 2261 if (!instObj) return prev; 2262 let currentDef; 2263 if (typeof instObj === "string") { 2264 currentDef = { 2265 svgContent: instObj, 2266 mountPoint: "body" 2267 }; 2268 } else { 2269 currentDef = instObj; 2270 } 2271 const transformField = isAccessory ? "accessoryTransform" : "transform"; 2272 const currentTransform = currentDef[transformField] || { x: 0, y: 0, scaleX: 1, scaleY: 1, rotation: 0 }; 2273 return { 2274 ...prev, 2275 [editingInstType]: { 2276 ...typeObj, 2277 [editingInstIndex]: { 2278 ...currentDef, 2279 [transformField]: { 2280 ...currentTransform, 2281 x: newX, 2282 y: newY 2283 } 2284 } 2285 } 2286 }; 2287 }); 2288 return; 2289 } 2290 if (draggingPoint.target === "action_keyframe") { 2291 const actionId = draggingPoint.actionId; 2292 const channelIdx = draggingPoint.channelIdx; 2293 const keyframeIdx = draggingPoint.keyframeIdx; 2294 const jx = draggingPoint.jx; 2295 const jy = draggingPoint.jy; 2296 const maxV = draggingPoint.maxV; 2297 const newTime = (svgP.x - (jx - 15)) / 30; 2298 const clampedTime = Math.round(Math.max(0, Math.min(1, newTime)) * 100) / 100; 2299 const newValue = (jy - 12 - svgP.y) / 8 * maxV; 2300 const clampedValue = Math.round(newValue * 10) / 10; 2301 const action = globalActions.find((a) => a.id === actionId); 2302 const channel = action == null ? void 0 : action.channels[channelIdx]; 2303 if (channel && channel.keyframes) { 2304 const nk = [...channel.keyframes]; 2305 nk[keyframeIdx] = { 2306 ...nk[keyframeIdx], 2307 time: clampedTime, 2308 value: clampedValue 2309 }; 2310 nk.sort((a, b) => a.time - b.time); 2311 const newIdx = nk.findIndex((k) => k.time === clampedTime && k.value === clampedValue); 2312 if (newIdx !== -1) { 2313 setDraggingPoint((prev) => ({ 2314 ...prev, 2315 keyframeIdx: newIdx 2316 })); 2317 } 2318 updateBodyChannel(actionId, channelIdx, { keyframes: nk }); 2319 } 2320 return; 2321 } 2322 if (draggingPoint.target === "action_wave_amp") { 2323 const actionId = draggingPoint.actionId; 2324 const channelIdx = draggingPoint.channelIdx; 2325 const isVertical = draggingPoint.isVertical; 2326 const isX = draggingPoint.isX; 2327 const jx = draggingPoint.jx; 2328 const jy = draggingPoint.jy; 2329 let newAmp = 0; 2330 if (isX) { 2331 newAmp = Math.abs(svgP.x - jx) / 0.3; 2332 } else if (isVertical) { 2333 newAmp = Math.abs(svgP.y - jy) / 0.3; 2334 } else { 2335 newAmp = Math.abs(svgP.y - (jy - 15)) / 0.3; 2336 } 2337 newAmp = Math.round(newAmp * 10) / 10; 2338 const action = globalActions.find((a) => a.id === actionId); 2339 const channel = action == null ? void 0 : action.channels[channelIdx]; 2340 if (channel) { 2341 const currentAmp = channel.amplitude || 10; 2342 const signedAmp = currentAmp < 0 ? -newAmp : newAmp;
2343 updateBodyChannel(actionId, channelIdx, { amplitude: signedAmp }); 2344 } 2345 return; 2346 } 2347 if (draggingPoint.target === "bodyPart" && selectedConfig) { 2348 const partId = draggingPoint.jointId; 2349 const deltaX = svgP.x - draggingPoint.mouseStartX; 2350 const deltaY = svgP.y - draggingPoint.mouseStartY; 2351 const charScale = selectedConfig.scale || 1; 2352 const newX = Math.round(draggingPoint.startX + deltaX / charScale); 2353 const newY = Math.round(draggingPoint.startY + deltaY / charScale); 2354 const currentTransform = ((_c2 = selectedConfig.transforms) == null ? void 0 : _c2[partId]) || { x: 0, y: 0, scaleX: 1, scaleY: 1, rotation: 0 }; 2355 handleConfigChange(selectedConfig.id, { 2356 transforms: { 2357 ...selectedConfig.transforms, 2358 [partId]: { 2359 ...currentTransform, 2360 x: newX, 2361 y: newY 2362 } 2363 } 2364 }); 2365 return; 2366 } 2367 setInstrumentConfigs((prev) => { 2368 const typeObj = prev[editingInstType]; 2369 const instObj = typeObj[editingInstIndex] || {}; 2370 const hand = draggingPoint.hand; 2371 draggingPoint.point; 2372 instObj[hand] || {}; 2373 if (draggingPoint.target === "headAnchor") { 2374 return { 2375 ...prev, 2376 [editingInstType]: { 2377 ...typeObj, 2378 [editingInstIndex]: { 2379 ...instObj, 2380 headAnchor: { x: localX, y: localY } 2381 } 2382 } 2383 }; 2384 } else if (draggingPoint.hand) { 2385 const hand2 = draggingPoint.hand; 2386 const point2 = draggingPoint.point; 2387 const motionObj2 = instObj[hand2] || { logic: "none", start: { x: 0, y: 0 }, end: { x: 0, y: 0 } }; 2388 let newRestPose = instObj.restPose || {}; 2389 if (point2 === "start") { 2390 newRestPose = { 2391 ...newRestPose, 2392 [hand2 === "leftHandMotion" ? "leftHand" : "rightHand"]: { x: localX, y: localY } 2393 }; 2394 } 2395 return { 2396 ...prev, 2397 [editingInstType]: { 2398 ...typeObj, 2399 [editingInstIndex]: { 2400 ...instObj, 2401 [hand2]: { ...motionObj2, [point2]: { x: localX, y: localY } }, 2402 restPose: newRestPose 2403 } 2404 } 2405 }; 2406 } 2407 return prev; 2408 }); 2409 }, 2410 onMouseUp: () => setDraggingPoint(null), 2411 onMouseLeave: () => setDraggingPoint(null), 2412 children: [ 2413 bg.type === "image" && ((_a = bg.image) == null ? void 0 : _a.url) && /* @__P
2413URE__ */ jsxRuntimeExports.jsx( 2414 "div", 2415 { 2416 className: "absolute inset-0 bg-cover bg-center", 2417 style: { 2418 ...imageStyle, 2419 zIndex: 1 2420 } 2421 } 2422 ), 2423 ((_b = bg.overlay) == null ? void 0 : _b.enabled) && /* @__PURE__ */ jsxRuntimeExports.jsx( 2424 "div", 2425 { 2426 className: "absolute inset-0 pointer-events-none", 2427 style: { 2428 backgroundColor: bg.overlay.color, 2429 opacity: bg.overlay.opacity, 2430 zIndex: 2 2431 } 2432 } 2433 ), 2434 ((_c = bg.particles) == null ? void 0 : _c.enabled) && /* @__PURE__ */ jsxRuntimeExports.jsx(BackgroundParticlesCanvas, { config: bg.particles, isPlaying: isPlaying || activeTab !== "actions" && playMode === "wave" }), 2435 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute top-4 right-4 flex items-center gap-2 z-20 select-none", children: [
2436 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex bg-zinc-950/90 backdrop-blur rounded border border-zinc-800 p-0.5 shadow-lg", children: [ 2437 /* @__PURE__ */ jsxRuntimeExports.jsx( 2438 "button", 2439 { 2440 onClick: undo, 2441 disabled: undoStack.length === 0, 2442 className: "p-1.5 hover:bg-zinc-800 rounded disabled:opacity-30 disabled:hover:bg-transparent text-zinc-300 hover:text-white transition-colors", 2443 title: "Undo (Ctrl+Z)", 2444 children: /* @__PURE__ */ jsxRuntimeExports.jsx(Undo, { className: "w-4 h-4" }) 2445 } 2446 ), 2447 /* @__PURE__ */ jsxRuntimeExports.jsx( 2448 "button", 2449 { 2450 onClick: redo, 2451 disabled: redoStack.length === 0, 2452 className: "p-1.5 hover:bg-zinc-800 rounded disabled:opacity-30 disabled:hover:bg-transparent text-zinc-300 hover:text-white transition-colors", 2453 title: "Redo (Ctrl+Y)", 2454 children: /* @__PURE__ */ jsxRuntimeExports.jsx(Redo, { className: "w-4 h-4" }) 2455 } 2456 ) 2457 ] }),
2458 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex bg-zinc-950/90 backdrop-blur rounded border border-zinc-800 p-0.5 shadow-lg", children: [ 2459 /* @__PURE__ */ jsxRuntimeExports.jsx( 2460 "button", 2461 { 2462 onClick: () => setViewZoom((prev) => Math.min(8, prev + 0.25)), 2463 className: "p-1.5 hover:bg-zinc-800 rounded text-zinc-300 hover:text-white transition-colors", 2464 title: "Zoom In", 2465 children: /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomIn, { className: "w-4 h-4" }) 2466 } 2467 ), 2468 /* @__PURE__ */ jsxRuntimeExports.jsx( 2469 "button", 2470 { 2471 onClick: () => setViewZoom((prev) => Math.max(0.25, prev - 0.25)), 2472 className: "p-1.5 hover:bg-zinc-800 rounded text-zinc-300 hover:text-white transition-colors", 2473 title: "Zoom Out", 2474 children: /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomOut, { className: "w-4 h-4" }) 2475 } 2476 ),
2477 /* @__PURE__ */ jsxRuntimeExports.jsx( 2478 "button", 2479 { 2480 onClick: () => { 2481 setViewZoom(1); 2482 setViewPanX(0); 2483 setViewPanY(0); 2484 }, 2485 className: "p-1.5 hover:bg-zinc-800 rounded text-zinc-300 hover:text-white transition-colors", 2486 title: "Reset View", 2487 children: /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { className: "w-4 h-4" }) 2488 } 2489 ) 2490 ] }) 2491 ] }), 2492 selectedPartId && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute bottom-4 left-4 bg-zinc-950/90 backdrop-blur text-xs px-2.5 py-1 rounded border border-zinc-800 text-zinc-400 select-none z-20 pointer-events-none animate-fadeIn", children: [ 2493 "Selected Part: ", 2494 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-indigo-400 font-mono font-bold", children: selectedPartId }) 2495 ] }),
2496 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute bottom-4 right-4 bg-zinc-950/90 backdrop-blur p-2 rounded border border-zinc-800 shadow-lg z-20 pointer-events-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [ 2497 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setIsPlaying(!isPlaying), className: "px-3 py-1 bg-indigo-600 hover:bg-indigo-500 rounded text-white font-bold text-xs", children: isPlaying ? "Pause" : "Play" }), 2498 activeTab !== "actions" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 text-xs", children: [ 2499 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-1 cursor-pointer text-zinc-300 hover:text-white select-none", children: [ 2500 /* @__PURE__ */ jsxRuntimeExports.jsx( 2501 "input", 2502 { 2503 type: "radio", 2504 name: "playMode", 2505 value: "instrument", 2506 checked: playMode === "instrument", 2507 onChange: () => setPlayMode("instrument"), 2508 className: "accent-indigo-600" 2509 } 2510 ), 2511 "Play" 2512 ] }),
2513 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-1 cursor-pointer text-zinc-300 hover:text-white select-none", children: [ 2514 /* @__PURE__ */ jsxRuntimeExports.jsx( 2515 "input", 2516 { 2517 type: "radio", 2518 name: "playMode", 2519 value: "wave", 2520 checked: playMode === "wave", 2521 onChange: () => setPlayMode("wave"), 2522 className: "accent-indigo-600" 2523 } 2524 ), 2525 "Wave" 2526 ] }) 2527 ] }), 2528 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-xs border-l border-zinc-700 pl-3 ml-1", children: [ 2529 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-500", children: "BPM:" }), 2530 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "range", min: "40", max: "240", value: tempo, onChange: (e) => setTempo(parseInt(e.target.value)), className: "w-20" }),
2531 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-8 text-right font-mono text-zinc-300", children: tempo }) 2532 ] }) 2533 ] }) }), 2534 animatorRef.current && /* @__PURE__ */ jsxRuntimeExports.jsxs( 2535 "svg", 2536 { 2537 ref: previewSvgRef, 2538 width: "100%", 2539 height: "100%", 2540 viewBox: `${-(80 / viewZoom) + viewPanX} ${100 * (activePreviewConfig.scale ?? 1) - 40 - 100 / viewZoom + viewPanY} ${160 / viewZoom} ${200 / viewZoom}`, 2541 className: `relative z-10 overflow-visible ${editMode !== "none" && activeTab === "characters" ? "cursor-crosshair" : ""}`, 2542 children: [ 2543 /* @__PURE__ */ jsxRuntimeExports.jsx( 2544 PuppetCharacter, 2545 { 2546 tickId, 2547 config: activePreviewConfig, 2548 state: animatorRef.current.getState(), 2549 x: 0, 2550 y: 0, 2551 isSoloed: false, 2552 isHidden: false, 2553 instrumentName: activeTab === "instruments" ? getInstrumentLabel(selectedInstType, selectedInstIndex.toString()) : "", 2554 showSkeleton, 2555 visibility, 2556 specificGraphic: activePreviewInst, 2557 clothingGraphic: activePreviewClothing, 2558 onClothingMouseDown: handleClothingMouseDown, 2559 onBodyPartMouseDown: handleBodyPartMouseDown, 2560 onInstrumentMouseDown: handleInstrumentMouseDown, 2561 isCharacterTab: activeTab === "characters", 2562 showActionPaths, 2563 activeTab, 2564 selectedActionId, 2565 globalActions, 2566 onActionOverlayMouseDown: setDraggingPoint, 2567 viewZoom, 2568 isPlaying: isPlaying || activeTab !== "actions" && playMode === "wave" 2569 } 2570 ), 2571 editMode !== "none" && /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { children: [ 2572 activeTab === "characters" && /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { pointerEvents: "none", opacity: "0.3", children: [
2573 /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "-1000", y1: "0", x2: "1000", y2: "0", stroke: "white", strokeWidth: "1", strokeDasharray: "5" }), 2574 /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "-1000", y1: 100 * (activePreviewConfig.scale ?? 1), x2: "1000", y2: 100 * (activePreviewConfig.scale ?? 1), stroke: "#10b981", strokeWidth: "1", strokeDasharray: "5", opacity: "0.5" }), 2575 /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "0", y1: "-1000", x2: "0", y2: "1000", stroke: "white", strokeWidth: "1", strokeDasharray: "5" }) 2576 ] }), 2577 (activeTab === "instruments" || activeTab === "characters" && previewInstKey) && /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { pointerEvents: "none", opacity: "0.3", children: [ 2578 /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "-1000", y1: "0", x2: "1000", y2: "0", stroke: "white", strokeWidth: "1", strokeDasharray: "5" }), 2579 /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "-1000", y1: 100 * (activePreviewConfig.scale ?? 1), x2: "1000", y2: 100 * (activePreviewConfig.scale ?? 1), stroke: "#10b981", strokeWidth: "1", strokeDasharray: "5", opacity: "0.5" }),
2580 /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "0", y1: "-1000", x2: "0", y2: "1000", stroke: "white", strokeWidth: "1", strokeDasharray: "5" }) 2581 ] }), 2582 (activeTab === "instruments" || activeTab === "characters" && previewInstKey) && (activePreviewInst == null ? void 0 : activePreviewInst.leftHandMotion) && (() => { 2583 const startProj = getProjectedPt(".custom-instrument-svg", activePreviewInst.leftHandMotion.start); 2584 const endProj = getProjectedPt(".custom-instrument-svg", activePreviewInst.leftHandMotion.end); 2585 return /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { children: [ 2586 /* @__PURE__ */ jsxRuntimeExports.jsx("line", { pointerEvents: "none", x1: startProj.x, y1: startProj.y, x2: endProj.x, y2: endProj.y, stroke: "#f43f5e", strokeWidth: "2", strokeDasharray: "4", opacity: "0.7" }), 2587 /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { onMouseDown: (e) => { 2588 e.stopPropagation(); 2589 setDraggingPoint({ hand: "leftHandMotion", point: "start" }); 2590 }, className: "cursor-grab active:cursor-grabbing", cx: startProj.x, cy: startProj.y, r: "5", fill: "#f43f5e", stroke: "white", strokeWidth: "1" }),
2591 /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { onMouseDown: (e) => { 2592 e.stopPropagation(); 2593 setDraggingPoint({ hand: "leftHandMotion", point: "end" }); 2594 }, className: "cursor-grab active:cursor-grabbing", cx: endProj.x, cy: endProj.y, r: "4", fill: "#111", stroke: "#f43f5e", strokeWidth: "2" }) 2595 ] }); 2596 })(), 2597 (activeTab === "instruments" || activeTab === "characters" && previewInstKey) && (activePreviewInst == null ? void 0 : activePreviewInst.rightHandMotion) && (() => { 2598 const startProj = getProjectedPt(".custom-instrument-svg", activePreviewInst.rightHandMotion.start); 2599 const endProj = getProjectedPt(".custom-instrument-svg", activePreviewInst.rightHandMotion.end); 2600 return /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { children: [ 2601 /* @__PURE__ */ jsxRuntimeExports.jsx("line", { pointerEvents: "none", x1: startProj.x, y1: startProj.y, x2: endProj.x, y2: endProj.y, stroke: "#3b82f6", strokeWidth: "2", strokeDasharray: "4", opacity: "0.7" }),
2602 /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { onMouseDown: (e) => { 2603 e.stopPropagation(); 2604 setDraggingPoint({ hand: "rightHandMotion", point: "start" }); 2605 }, className: "cursor-grab active:cursor-grabbing", cx: startProj.x, cy: startProj.y, r: "5", fill: "#3b82f6", stroke: "white", strokeWidth: "1" }), 2606 /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { onMouseDown: (e) => { 2607 e.stopPropagation(); 2608 setDraggingPoint({ hand: "rightHandMotion", point: "end" }); 2609 }, className: "cursor-grab active:cursor-grabbing", cx: endProj.x, cy: endProj.y, r: "4", fill: "#111", stroke: "#3b82f6", strokeWidth: "2" }) 2610 ] }); 2611 })(), 2612 activeTab === "characters" && activePreviewConfig && (() => { 2613 const svgCoords = parseSvgSocketsAndPlugs(activePreviewConfig.svgContent); 2614 const sockets = {};
2615 Object.entries(svgCoords).forEach(([key, pt]) => { 2616 if (key.startsWith("socket_")) { 2617 sockets[key.replace("socket_", "")] = pt; 2618 } 2619 }); 2620 if (activePreviewConfig.sockets) { 2621 Object.entries(activePreviewConfig.sockets).forEach(([key, pt]) => { 2622 sockets[key] = pt; 2623 }); 2624 } 2625 return Object.entries(sockets).map(([name, pt]) => { 2626 const isSelected = editMode === `socket_${name}`; 2627 const projected = getProjectedPt(".custom-character-svg", pt); 2628 return /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { transform: `translate(${projected.x}, ${projected.y})`, children: [ 2629 /* @__PURE__ */ jsxRuntimeExports.jsx( 2630 "circle", 2631 { 2632 r: (isSelected ? 5 : 3.5) / viewZoom, 2633 fill: isSelected ? "#10b981" : "#6366f1", 2634 stroke: "white", 2635 strokeWidth: (isSelected ? 1.5 : 0.75) / viewZoom, 2636 className: "cursor-grab active:cursor-grabbing", 2637 onMouseDown: (e) => { 2638 e.stopPropagation(); 2639 e.preventDefault(); 2640 pushUndo(); 2641 setEditMode(`socket_${name}`); 2642 setDraggingPoint({ 2643 target: "socket", 2644 jointId: name 2645 }); 2646 } 2647 } 2648 ),
2649 /* @__PURE__ */ jsxRuntimeExports.jsx( 2650 "text", 2651 { 2652 y: -7 / viewZoom, 2653 textAnchor: "middle", 2654 fill: isSelected ? "#10b981" : "#a5b4fc", 2655 fontSize: 6 / viewZoom, 2656 fontWeight: "bold", 2657 className: "pointer-events-none select-none", 2658 style: { paintOrder: "stroke", stroke: "#000", strokeWidth: `${1.5 / viewZoom}px` }, 2659 children: name 2660 } 2661 ) 2662 ] }, name); 2663 }); 2664 })(), 2665 (activeTab === "instruments" || activeTab === "characters" && previewInstKey) && activePreviewInst && (() => { 2666 const svgCoords = parseSvgSocketsAndPlugs(activePreviewInst.svgContent); 2667 const plugs = {}; 2668 const defaultPlugs = getFamilyDefaultPlugs(activePreviewInst.family || "other"); 2669 const configPlugs = Object.keys(activePreviewInst.plugs || {}); 2670 const allPlugs = Array.from(/* @__PURE__ */ new Set([...defaultPlugs, ...configPlugs])); 2671 allPlugs.forEach((name) => { 2672 var _a2; 2673 const svgVal = svgCoords[`plug_${name}`]; 2674 const configVal = (_a2 = activePreviewInst.plugs) == null ? void 0 : _a2[name]; 2675 const val = configVal || svgVal; 2676 if (val) plugs[name] = val; 2677 }); 2678 return Object.entries(plugs).map(([name, pt]) => { 2679 const projected = getProjectedPt(".custom-instrument-svg", pt); 2680 const isSelected = editMode === `plug_${name}`; 2681 return /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { transform: `translate(${projected.x}, ${projected.y})`, children: [
2682 /* @__PURE__ */ jsxRuntimeExports.jsx( 2683 "circle", 2684 { 2685 r: (isSelected ? 5 : 3.5) / viewZoom, 2686 fill: isSelected ? "#10b981" : "#f59e0b", 2687 stroke: "white", 2688 strokeWidth: (isSelected ? 1.5 : 0.75) / viewZoom, 2689 className: "cursor-grab active:cursor-grabbing", 2690 onMouseDown: (e) => { 2691 e.stopPropagation(); 2692 e.preventDefault(); 2693 pushUndo(); 2694 setEditMode(`plug_${name}`); 2695 setDraggingPoint({ 2696 target: "plug", 2697 jointId: name 2698 }); 2699 } 2700 } 2701 ), 2702 /* @__P
2702URE__ */ jsxRuntimeExports.jsx( 2703 "text", 2704 { 2705 y: -7 / viewZoom, 2706 textAnchor: "middle", 2707 fill: isSelected ? "#10b981" : "#fcd34d", 2708 fontSize: 6 / viewZoom, 2709 fontWeight: "bold", 2710 className: "pointer-events-none select-none", 2711 style: { paintOrder: "stroke", stroke: "#000", strokeWidth: `${1.5 / viewZoom}px` }, 2712 children: name 2713 } 2714 ) 2715 ] }, name); 2716 }); 2717 })(), 2718 activeTab === "clothing" && activePreviewClothing && (() => { 2719 const overlays = []; 2720 Object.keys(activePreviewClothing.parts).forEach((part) => { 2721 var _a2, _b2, _c2, _d2, _e2; 2722 const glue = (_a2 = activePreviewClothing.gluing) == null ? void 0 : _a2[part]; 2723 if (!glue || glue.type === "none") return; 2724 const svgContent = activePreviewClothing.parts[part] || ""; 2725 const svgCoords = parseSvgSocketsAndPlugs(svgContent); 2726 const p1Val = ((_c2 = (_b2 = activePreviewClothing.plugs) == null ? void 0 : _b2[part]) == null ? void 0 : _c2.p1) || svgCoords["plug_p1"] || glue.p1; 2727 const p2Val = ((_e2 = (_d2 = activePreviewClothing.plugs) == null ? void 0 : _d2[part]) == null ? void 0 : _e2.p2) || svgCoords["plug_p2"] || glue.p2; 2728 if (p1Val) { 2729 const projected = getProjectedPt(`.custom-clothing-mount[data-target-joint="${part}"] .custom-clothing-offset`, p1Val); 2730 const keyName = `clothing_plug_p1_${part}`; 2731 const isSelected = editMode === keyName; 2732 overlays.push(
2733 /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { transform: `translate(${projected.x}, ${projected.y})`, children: [ 2734 /* @__PURE__ */ jsxRuntimeExports.jsx( 2735 "circle", 2736 { 2737 r: (isSelected ? 5 : 3.5) / viewZoom, 2738 fill: isSelected ? "#10b981" : "#ec4899", 2739 stroke: "white", 2740 strokeWidth: (isSelected ? 1.5 : 0.75) / viewZoom, 2741 className: "cursor-grab active:cursor-grabbing", 2742 onMouseDown: (e) => { 2743 e.stopPropagation(); 2744 e.preventDefault(); 2745 pushUndo(); 2746 setEditMode(keyName); 2747 setDraggingPoint({ target: "clothing_plug", plugPart: part, plugKey: "p1" }); 2748 } 2749 } 2750 ),
2751 /* @__PURE__ */ jsxRuntimeExports.jsxs( 2752 "text", 2753 { 2754 y: -7 / viewZoom, 2755 textAnchor: "middle", 2756 fill: isSelected ? "#10b981" : "#fbcfe8", 2757 fontSize: 6 / viewZoom, 2758 fontWeight: "bold", 2759 className: "pointer-events-none select-none", 2760 style: { paintOrder: "stroke", stroke: "#000", strokeWidth: `${1.5 / viewZoom}px` }, 2761 children: [ 2762 part, 2763 "_p1" 2764 ] 2765 } 2766 ) 2767 ] }, `${part}_p1`) 2768 ); 2769 } 2770 if (glue.type === "two-point" && p2Val) { 2771 const projected = getProjectedPt(`.custom-clothing-mount[data-target-joint="${part}"] .custom-clothing-offset`, p2Val); 2772 const keyName = `clothing_plug_p2_${part}`; 2773 const isSelected = editMode === keyName; 2774 overlays.push(
2775 /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { transform: `translate(${projected.x}, ${projected.y})`, children: [ 2776 /* @__PURE__ */ jsxRuntimeExports.jsx( 2777 "circle", 2778 { 2779 r: (isSelected ? 5 : 3.5) / viewZoom, 2780 fill: isSelected ? "#10b981" : "#ec4899", 2781 stroke: "white", 2782 strokeWidth: (isSelected ? 1.5 : 0.75) / viewZoom, 2783 className: "cursor-grab active:cursor-grabbing", 2784 onMouseDown: (e) => { 2785 e.stopPropagation(); 2786 e.preventDefault(); 2787 pushUndo(); 2788 setEditMode(keyName); 2789 setDraggingPoint({ target: "clothing_plug", plugPart: part, plugKey: "p2" }); 2790 } 2791 } 2792 ),
2793 /* @__PURE__ */ jsxRuntimeExports.jsxs( 2794 "text", 2795 { 2796 y: -7 / viewZoom, 2797 textAnchor: "middle", 2798 fill: isSelected ? "#10b981" : "#fbcfe8", 2799 fontSize: 6 / viewZoom, 2800 fontWeight: "bold", 2801 className: "pointer-events-none select-none", 2802 style: { paintOrder: "stroke", stroke: "#000", strokeWidth: `${1.5 / viewZoom}px` }, 2803 children: [ 2804 part, 2805 "_p2" 2806 ] 2807 } 2808 ) 2809 ] }, `${part}_p2`) 2810 ); 2811 } 2812 }); 2813 return overlays; 2814 })() 2815 ] }) 2816 ] 2817 } 2818 ) 2819 ] 2820 } 2821 ) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-full flex items-center justify-center text-zinc-600", children: "Select a config to preview" }) }),
2822 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-[500px] border-l border-zinc-800 overflow-y-auto p-6 bg-[#050505] custom-scrollbar flex flex-col", children: [ 2823 activePreviewConfig && activeTab !== "background" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-6 border border-zinc-800 rounded-lg bg-[#0a0a0a] overflow-hidden flex flex-col shrink-0", children: [ 2824 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-3 border-b border-zinc-800 bg-zinc-900 flex items-center justify-between", children: [ 2825 /* @__PURE__ */ jsxRuntimeExports.jsxs( 2826 "div", 2827 { 2828 className: "flex items-center gap-2 cursor-pointer hover:text-white transition-colors text-zinc-400 font-bold text-sm", 2829 onClick: () => setIsOptionsCollapsed(!isOptionsCollapsed), 2830 children: [ 2831 isOptionsCollapsed ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 16 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { size: 16 }),
2832 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "IK Preview" }) 2833 ] 2834 } 2835 ), 2836 /* @__PURE__ */ jsxRuntimeExports.jsxs( 2837 "button", 2838 { 2839 onClick: () => setShowSkeleton(!showSkeleton), 2840 className: `flex items-center gap-1 text-xs px-2 py-1 rounded ${showSkeleton ? "bg-indigo-600/30 text-indigo-400" : "bg-zinc-800 text-zinc-500"}`, 2841 children: [ 2842 /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { size: 12 }), 2843 " ", 2844 showSkeleton ? "Hide Skeleton" : "Show Skeleton" 2845 ] 2846 } 2847 ), 2848 /* @__PURE__ */ jsxRuntimeExports.jsxs( 2849 "button", 2850 { 2851 onClick: () => setShowActionPaths(!showActionPaths), 2852 className: `flex items-center gap-1 text-xs px-2 py-1 rounded ${showActionPaths ? "bg-emerald-600/30 text-emerald-400" : "bg-zinc-800 text-zinc-500"}`, 2853 children: [
2854 /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { size: 12 }), 2855 " ", 2856 showActionPaths ? "Hide Actions" : "Show Actions" 2857 ] 2858 } 2859 ) 2860 ] }), 2861 !isOptionsCollapsed && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-3 border-b border-zinc-800 bg-[#111] grid grid-cols-2 gap-2 text-xs", children: [ 2862 activeTab !== "characters" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "col-span-2 flex items-center justify-between bg-zinc-900 p-2 rounded border border-zinc-800 mb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 w-full", children: [ 2863 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-zinc-500 text-xs font-bold uppercase tracking-wider whitespace-nowrap", children: activeTab === "instruments" ? "Assigned Character:" : "Preview Character:" }), 2864 /* @__PURE__ */ jsxRuntimeExports.jsxs( 2865 "select", 2866 { 2867 value: activeTab === "instruments" && (selectedInstSvg == null ? void 0 : selectedInstSvg.characterId) ? selectedInstSvg.characterId : previewCharId, 2868 onChange: (e) => { 2869 const val = e.target.value; 2870 setPreviewCharId(val); 2871 if (activeTab === "instruments" && selectedInstKey) { 2872 const instType = selectedInstKey.startsWith("m_") ? "melodic" : "percussion"; 2873 const instIndex = parseInt(selectedInstKey.split("_")[1]); 2874 const currentInst = instrumentConfigs[instType][instIndex] || { svgContent: "", mountPoint: "body" }; 2875 setInstrumentConfigs((prev) => ({ 2876 ...prev, 2877 [instType]: { 2878 ...prev[instType], 2879 [instIndex]: { 2880 ...typeof currentInst === "string" ? { svgContent: currentInst, mountPoint: "body" } : currentInst, 2881 characterId: val === "" ? void 0 : val 2882 } 2883 } 2884 })); 2885 } 2886 }, 2887 className: "bg-[#111] border border-zinc-700 rounded p-1 text-white text-xs flex-1", 2888 children: [ 2889 activeTab === "instruments" && /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "(Default for Family)" }), 2890 Object.keys(configs).map((k) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: k, children: configs[k].name }, k)) 2891 ] 2892 } 2893 ) 2894 ] }) }), 2895 activeTab !== "instruments" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "col-span-2 flex items-center justify-between bg-zinc-900 p-2 rounded border border-zinc-800 mb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 w-full", children: [
2896 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-zinc-500 text-xs font-bold uppercase tracking-wider whitespace-nowrap", children: "Preview Instrument:" }), 2897 /* @__PURE__ */ jsxRuntimeExports.jsxs( 2898 "select", 2899 { 2900 value: previewInstKey || "", 2901 onChange: (e) => setPreviewInstKey(e.target.value || null), 2902 className: "bg-[#111] border border-zinc-700 rounded p-1 text-white text-xs flex-1", 2903 children: [ 2904 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "None" }), 2905 /* @__PURE__ */ jsxRuntimeExports.jsx("optgroup", { label: "Melodic", children: Object.keys(instrumentConfigs.melodic).map((k) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: `m_${k}`, children: getInstrumentLabel("melodic", k) }, `m_${k}`)) }), 2906 /* @__PURE__ */ jsxRuntimeExports.jsx("optgroup", { label: "Percussion", children: Object.keys(instrumentConfigs.percussion).map((k) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: `p_${k}`, children: getInstrumentLabel("percussion", k) }, `p_${k}`)) }) 2907 ] 2908 } 2909 ) 2910 ] }) }), 2911 activeTab !== "clothing" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "col-span-2 flex items-center justify-between bg-zinc-900 p-2 rounded border border-zinc-800 mb-2", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 w-full", children: [
2912 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-zinc-500 text-xs font-bold uppercase tracking-wider whitespace-nowrap", children: activeTab === "instruments" ? "Assigned Clothing:" : "Preview Clothing:" }), 2913 /* @__PURE__ */ jsxRuntimeExports.jsxs( 2914 "select", 2915 { 2916 value: activeTab === "instruments" && (selectedInstSvg == null ? void 0 : selectedInstSvg.clothingId) ? selectedInstSvg.clothingId : previewClothingId || "", 2917 onChange: (e) => { 2918 const val = e.target.value || null; 2919 setPreviewClothingId(val); 2920 if (activeTab === "instruments" && selectedInstKey) { 2921 const instType = selectedInstKey.startsWith("m_") ? "melodic" : "percussion"; 2922 const instIndex = parseInt(selectedInstKey.split("_")[1]); 2923 const currentInst = instrumentConfigs[instType][instIndex] || { svgContent: "", mountPoint: "body" }; 2924 setInstrumentConfigs((prev) => ({ 2925 ...prev, 2926 [instType]: { 2927 ...prev[instType], 2928 [instIndex]: { 2929 ...typeof currentInst === "string" ? { svgContent: currentInst, mountPoint: "body" } : currentInst, 2930 clothingId: val === null ? void 0 : val 2931 } 2932 } 2933 })); 2934 } 2935 }, 2936 className: "bg-[#111] border border-zinc-700 rounded p-1 text-white text-xs flex-1", 2937 children: [
2938 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: activeTab === "instruments" ? "(Randomly Assigned)" : "None" }), 2939 activeTab === "instruments" && /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "none", children: "No Clothing" }), 2940 Object.keys(clothingConfigs).map((k) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: k, children: k }, k)) 2941 ] 2942 } 2943 ) 2944 ] }) }), 2945 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "col-span-2 space-y-1 mt-2", children: [ 2946 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-zinc-500 font-bold mb-1 uppercase tracking-wider text-[10px]", children: "Parts Visibility & Z-Layer Order" }), 2947 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "overflow-x-auto rounded border border-zinc-800 bg-zinc-950", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { className: "w-full text-left text-xs border-collapse", children: [
2948 /* @__PURE__ */ jsxRuntimeExports.jsx("thead", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "border-b border-zinc-800 bg-zinc-900 text-zinc-400 font-semibold text-[10px]", children: [ 2949 /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "p-2", children: "Part" }), 2950 /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "p-2 text-center", children: "Visible" }), 2951 /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "p-2 text-center w-24", children: "Z-Order" }) 2952 ] }) }), 2953 /* @__PURE__ */ jsxRuntimeExports.jsx("tbody", { children: (() => { 2954 const staticOrder = [ 2955 "head", 2956 "clothing_head", 2957 "body", 2958 "clothing_body", 2959 "upper_arm_l", 2960 "clothing_upper_arm_l", 2961 "lower_arm_l", 2962 "clothing_lower_arm_l", 2963 "hand_l", 2964 "clothing_hand_l", 2965 "upper_arm_r", 2966 "clothing_upper_arm_r", 2967 "lower_arm_r", 2968 "clothing_lower_arm_r", 2969 "hand_r", 2970 "clothing_hand_r", 2971 "leg_l", 2972 "clothing_leg_l", 2973 "foot_l", 2974 "clothing_foot_l", 2975 "leg_r", 2976 "clothing_leg_r", 2977 "foot_r", 2978 "clothing_foot_r", 2979 "instrument" 2980 ]; 2981 const sortKeys = (keys) => { 2982 return [...keys].sort((a, b) => { 2983 let idxA = staticOrder.indexOf(a); 2984 let idxB = staticOrder.indexOf(b); 2985 if (idxA === -1) idxA = 999; 2986 if (idxB === -1) idxB = 999; 2987 return idxA - idxB; 2988 }); 2989 }; 2990 const bodyParts = [ 2991 "head", 2992 "body", 2993 "upper_arm_l", 2994 "lower_arm_l", 2995 "hand_l", 2996 "upper_arm_r", 2997 "lower_arm_r", 2998 "hand_r", 2999 "leg_l", 3000 "foot_l", 3001 "leg_r", 3002 "foot_r" 3003 ]; 3004 const allKeys = Object.keys(visibility); 3005 const groups = [ 3006 { 3007 id: "character", 3008 name: "Character", 3009 icon: "ð¤", 3010 keys: allKeys.filter((k) => !k.startsWith("clothing_") && k !== "instrument" && bodyParts.includes(k)) 3011 }, 3012 { 3013 id: "clothing", 3014 name: "Clothing", 3015 icon: "ð", 3016 keys: allKeys.filter((k) => k.startsWith("clothing_")) 3017 }, 3018 { 3019 id: "accessory", 3020 name: "Accessory", 3021 icon: "ð¸", 3022 keys: allKeys.filter((k) => !k.startsWith("clothing_") && k !== "instrument" && !bodyParts.includes(k)).concat(allKeys.includes("instrument") ? ["instrument"] : []) 3023 } 3024 ]; 3025 return groups.map((group) => { 3026 const isCollapsed = collapsedGroups[group.id]; 3027 const sortedKeys = sortKeys(group.keys); 3028 return /* @__PURE__ */ jsxRuntimeExports.jsxs(React.Fragment, { children: [
3029 /* @__PURE__ */ jsxRuntimeExports.jsxs( 3030 "tr", 3031 { 3032 className: "bg-zinc-900/80 border-b border-zinc-800 cursor-pointer select-none hover:bg-zinc-900 text-zinc-300 font-bold", 3033 onClick: () => setCollapsedGroups((prev) => ({ ...prev, [group.id]: !prev[group.id] })), 3034 children: [ 3035 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "p-2 text-[11px] hover:text-white transition-colors", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 3036 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-zinc-500 w-3 inline-block", children: isCollapsed ? "â¶" : "â¼" }), 3037 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: group.icon }), 3038 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: group.name }),
3039 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-zinc-500 font-normal text-[10px]", children: [ 3040 "(", 3041 sortedKeys.length, 3042 " items)" 3043 ] }) 3044 ] }) }), 3045 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "p-2 text-center", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxRuntimeExports.jsx( 3046 "input", 3047 { 3048 type: "checkbox", 3049 checked: group.keys.length > 0 && group.keys.every((k) => !!visibility[k]), 3050 onChange: (e) => { 3051 const targetVal = e.target.checked; 3052 if (!selectedId) return; 3053 pushUndo(); 3054 const next = { ...visibility }; 3055 group.keys.forEach((k) => { 3056 next[k] = targetVal; 3057 }); 3058 setVisibility(next); 3059 handleConfigChange(selectedId, { visibility: next }); 3060 }, 3061 className: "rounded bg-zinc-850 border-zinc-700 text-indigo-600 focus:ring-indigo-500 cursor-pointer pointer-events-auto relative z-50" 3062 } 3063 ) }),
3064 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "p-2 w-24" }) 3065 ] 3066 } 3067 ), 3068 !isCollapsed && sortedKeys.map((part) => { 3069 var _a2, _b2; 3070 const defaults = { 3071 leg_l: 1, 3072 clothing_leg_l: 1.5, 3073 leg_r: 2, 3074 clothing_leg_r: 2.5, 3075 foot_l: 3, 3076 foot_r: 4, 3077 body: 5, 3078 clothing_body: 5.5, 3079 head: 6, 3080 clothing_head: 6.5, 3081 upper_arm_r: 7, 3082 clothing_upper_arm_r: 7.2, 3083 lower_arm_r: 8, 3084 clothing_lower_arm_r: 8.2, 3085 hand_r: 9, 3086 clothing_hand_r: 9.2, 3087 instrument: 10, 3088 upper_arm_l: 11, 3089 clothing_upper_arm_l: 11.2, 3090 lower_arm_l: 12, 3091 clothing_lower_arm_l: 12.2, 3092 hand_l: 13, 3093 clothing_hand_l: 13.2 3094 }; 3095 const isZEditable = activeTab === "characters" ? !!selectedId : !!activePreviewInst; 3096 const currentZ = activeTab === "characters" ? ((_a2 = selectedConfig == null ? void 0 : selectedConfig.baseZOrder) == null ? void 0 : _a2[part]) ?? defaults[part] ?? 0 : ((_b2 = activePreviewInst == null ? void 0 : activePreviewInst.zOrder) == null ? void 0 : _b2[part]) ?? defaults[part] ?? 0; 3097 return /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "border-b border-zinc-900 hover:bg-zinc-900/40 bg-zinc-950/20", children: [
3098 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "p-2 pl-6 text-zinc-300 capitalize font-medium text-[11px]", children: part.startsWith("clothing_") ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [ 3099 part.replace("clothing_", "Clothing ").replace("_l", " (L)").replace("_r", " (R)"), 3100 " ", 3101 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-zinc-500 font-normal lowercase italic text-[10px]", children: [ 3102 "(linked to ", 3103 part.replace("clothing_", "").replace("_l", " (L)").replace("_r", " (R)"), 3104 ")" 3105 ] }) 3106 ] }) : part.replace("_l", " (L)").replace("_r", " (R)") }), 3107 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "p-2 text-center", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxRuntimeExports.jsx( 3108 "input", 3109 { 3110 type: "checkbox", 3111 checked: !!visibility[part], 3112 onChange: () => { 3113 if (!selectedId) return; 3114 pushUndo(); 3115 const next = { ...visibility, [part]: !visibility[part] }; 3116 setVisibility(next); 3117 handleConfigChange(selectedId, { visibility: next }); 3118 }, 3119 className: "rounded bg-zinc-850 border-zinc-700 text-indigo-600 focus:ring-indigo-500 cursor-pointer pointer-events-auto relative z-50" 3120 } 3121 ) }),
3122 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "p-2 text-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx( 3123 "input", 3124 { 3125 type: "number", 3126 step: "any", 3127 value: currentZ, 3128 disabled: !isZEditable, 3129 onChange: (e) => { 3130 const val = parseFloat(e.target.value) || 0; 3131 if (activeTab === "characters") { 3132 if (!selectedId) return; 3133 pushUndo(); 3134 const currentBaseZ = (selectedConfig == null ? void 0 : selectedConfig.baseZOrder) || {}; 3135 const nextBaseZ = { ...currentBaseZ, [part]: val }; 3136 handleConfigChange(selectedId, { baseZOrder: nextBaseZ }); 3137 } else { 3138 handleZOrderChange(part, val); 3139 } 3140 }, 3141 className: "w-16 bg-zinc-900 border border-zinc-800 rounded px-1 py-0.5 text-center text-zinc-200 text-xs font-mono disabled:opacity-40 disabled:cursor-not-allowed" 3142 } 3143 ) }) 3144 ] }, part); 3145 }) 3146 ] }, group.id); 3147 }); 3148 })() }) 3149 ] }) }), 3150 !activePreviewInst && activeTab !== "characters" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-zinc-500 italic mt-1", children: "Select an instrument to edit Z-order values." }) 3151 ] }), 3152 (activeTab === "instruments" || activeTab === "characters" && previewInstKey) && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "col-span-2 grid grid-cols-2 gap-2 mt-2", children: [
3153 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 3154 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-zinc-500 font-bold mb-1 uppercase tracking-wider text-[10px]", children: "Left Hand Logic" }), 3155 /* @__PURE__ */ jsxRuntimeExports.jsxs( 3156 "select", 3157 { 3158 value: ((_d = activePreviewInst == null ? void 0 : activePreviewInst.leftHandMotion) == null ? void 0 : _d.logic) || "none", 3159 onChange: (e) => { 3160 const logic = e.target.value; 3161 setInstrumentConfigs((prev) => { 3162 const typeObj = prev[editingInstType]; 3163 const instObj = typeObj[editingInstIndex] || {}; 3164 const m = instObj.leftHandMotion || { logic: "none", start: { x: 0, y: 0 }, end: { x: 0, y: 0 } }; 3165 return { ...prev, [editingInstType]: { ...typeObj, [editingInstIndex]: { ...instObj, leftHandMotion: { ...m, logic } } } }; 3166 }); 3167 }, 3168 className: "w-full bg-zinc-900 border border-zinc-700 rounded p-1 text-white text-[10px]", 3169 children: [
3170 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "none", children: "None (Fixed Point)" }), 3171 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "oscillate", children: "Oscillate (Sine)" }), 3172 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "strike", children: "Strike (Bounce)" }), 3173 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "pitch_map", children: "Pitch Map (Slide)" }), 3174 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "phrase_bow", children: "Phrase Bow" }) 3175 ] 3176 } 3177 ) 3178 ] }), 3179 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 3180 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-zinc-500 font-bold mb-1 uppercase tracking-wider text-[10px]", children: "Right Hand Logic" }), 3181 /* @__PURE__ */ jsxRuntimeExports.jsxs( 3182 "select", 3183 { 3184 value: ((_e = activePreviewInst == null ? void 0 : activePreviewInst.rightHandMotion) == null ? void 0 : _e.logic) || "none", 3185 onChange: (e) => { 3186 const logic = e.target.value; 3187 setInstrumentConfigs((prev) => { 3188 const typeObj = prev[editingInstType]; 3189 const instObj = typeObj[editingInstIndex] || {}; 3190 const m = instObj.rightHandMotion || { logic: "none", start: { x: 0, y: 0 }, end: { x: 0, y: 0 } }; 3191 return { ...prev, [editingInstType]: { ...typeObj, [editingInstIndex]: { ...instObj, rightHandMotion: { ...m, logic } } } }; 3192 }); 3193 }, 3194 className: "w-full bg-zinc-900 border border-zinc-700 rounded p-1 text-white text-[10px]", 3195 children: [
3196 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "none", children: "None (Fixed Point)" }), 3197 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "oscillate", children: "Oscillate (Sine)" }), 3198 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "strike", children: "Strike (Bounce)" }), 3199 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "pitch_map", children: "Pitch Map (Slide)" }), 3200 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "phrase_bow", children: "Phrase Bow" }) 3201 ] 3202 } 3203 ) 3204 ] }) 3205 ] }), 3206 (activeTab === "instruments" || activeTab === "characters" && previewInstKey) && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "col-span-2 mt-3 border-t border-zinc-800 pt-3 space-y-2", children: [ 3207 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-zinc-500 font-bold mb-2 uppercase tracking-wider text-[10px]", children: "Body Behavior (when playing)" }), 3208 [ 3209 { key: "bodySway", label: "Body Sway", defaultVal: 20, max: 60, step: 0.5 }, 3210 { key: "headSway", label: "Head Sway", defaultVal: 1, max: 5, step: 0.1 }, 3211 { key: "fingerFlutter", label: "Finger Flutter", defaultVal: 2, max: 10, step: 0.5 }, 3212 { key: "grooveSway", label: "Groove Sway", defaultVal: 1, max: 3, step: 0.1 }, 3213 { key: "headRotation", label: "Head Rotation", defaultVal: 1, max: 3, step: 0.1 }, 3214 { key: "footTap", label: "Foot Tap", defaultVal: 1, max: 3, step: 0.1 } 3215 ].map(({ key, label, defaultVal, max, step }) => { 3216 var _a2; 3217 const currentVal = ((_a2 = activePreviewInst == null ? void 0 : activePreviewInst.bodyBehavior) == null ? void 0 : _a2[key]) ?? defaultVal; 3218 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
3219 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-zinc-400 text-[10px] w-24 shrink-0", children: label }), 3220 /* @__PURE__ */ jsxRuntimeExports.jsx( 3221 "input", 3222 { 3223 type: "range", 3224 min: 0, 3225 max, 3226 step, 3227 value: currentVal, 3228 onChange: (e) => { 3229 const val = parseFloat(e.target.value); 3230 setInstrumentConfigs((prev) => { 3231 const typeObj = prev[editingInstType]; 3232 const instObj = typeObj[editingInstIndex] || {}; 3233 const bb = instObj.bodyBehavior || {}; 3234 return { ...prev, [editingInstType]: { ...typeObj, [editingInstIndex]: { ...instObj, bodyBehavior: { ...bb, [key]: val } } } }; 3235 }); 3236 }, 3237 className: "flex-1 accent-indigo-500" 3238 } 3239 ),
3240 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-zinc-300 text-[10px] w-8 text-right", children: currentVal }), 3241 /* @__PURE__ */ jsxRuntimeExports.jsx( 3242 "button", 3243 { 3244 onClick: () => { 3245 setInstrumentConfigs((prev) => { 3246 const typeObj = prev[editingInstType]; 3247 const instObj = typeObj[editingInstIndex] || {}; 3248 const bb = { ...instObj.bodyBehavior || {} }; 3249 bb[key] = defaultVal; 3250 return { ...prev, [editingInstType]: { ...typeObj, [editingInstIndex]: { ...instObj, bodyBehavior: bb } } }; 3251 }); 3252 }, 3253 title: "Reset to default", 3254 className: "text-zinc-600 hover:text-zinc-300 text-[9px]", 3255 children: "âº" 3256 } 3257 ) 3258 ] }, key); 3259 }),
3260 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-4 text-zinc-500 font-bold mb-2 uppercase tracking-wider text-[10px]", children: "Idle Behavior (when not playing)" }), 3261 [ 3262 { key: "bodySway", label: "Body Sway (Mult)", defaultVal: 1, max: 2, step: 0.1 }, 3263 { key: "headSway", label: "Head Sway (Mult)", defaultVal: 1, max: 2, step: 0.1 }, 3264 { key: "armSway", label: "Arm Sway (Mult)", defaultVal: 1, max: 2, step: 0.1 } 3265 ].map(({ key, label, defaultVal, max, step }) => { 3266 var _a2; 3267 const currentVal = ((_a2 = activePreviewInst == null ? void 0 : activePreviewInst.idleBehavior) == null ? void 0 : _a2[key]) ?? defaultVal; 3268 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 3269 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-zinc-400 text-[10px] w-24 shrink-0", children: label }), 3270 /* @__PURE__ */ jsxRuntimeExports.jsx( 3271 "input", 3272 { 3273 type: "range", 3274 min: 0, 3275 max, 3276 step, 3277 value: currentVal, 3278 onChange: (e) => { 3279 const val = parseFloat(e.target.value); 3280 setInstrumentConfigs((prev) => { 3281 const typeObj = prev[editingInstType]; 3282 const instObj = typeObj[editingInstIndex] || {}; 3283 const ib = instObj.idleBehavior || {}; 3284 return { ...prev, [editingInstType]: { ...typeObj, [editingInstIndex]: { ...instObj, idleBehavior: { ...ib, [key]: val } } } }; 3285 }); 3286 }, 3287 className: "flex-1 accent-indigo-500" 3288 } 3289 ),
3290 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-zinc-300 text-[10px] w-8 text-right", children: currentVal }), 3291 /* @__PURE__ */ jsxRuntimeExports.jsx( 3292 "button", 3293 { 3294 onClick: () => { 3295 setInstrumentConfigs((prev) => { 3296 const typeObj = prev[editingInstType]; 3297 const instObj = typeObj[editingInstIndex] || {}; 3298 const ib = { ...instObj.idleBehavior || {} }; 3299 ib[key] = defaultVal; 3300 return { ...prev, [editingInstType]: { ...typeObj, [editingInstIndex]: { ...instObj, idleBehavior: ib } } }; 3301 }); 3302 }, 3303 title: "Reset to default", 3304 className: "text-zinc-600 hover:text-zinc-300 text-[9px]", 3305 children: "âº" 3306 } 3307 ) 3308 ] }, key); 3309 }),
3310 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-3 border-t border-zinc-800 pt-3 space-y-2", children: [ 3311 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-zinc-500 font-bold mb-1 uppercase tracking-wider text-[10px]", children: "Head/Mouth Anchor" }), 3312 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between text-xs", children: [ 3313 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-400", children: "Lock head to instrument (mouthpiece)" }), 3314 /* @__PURE__ */ jsxRuntimeExports.jsx( 3315 "button", 3316 { 3317 onClick: () => { 3318 setInstrumentConfigs((prev) => { 3319 const typeObj = prev[editingInstType]; 3320 const instObj = typeObj[editingInstIndex] || {}; 3321 const newInst = { ...instObj }; 3322 if (newInst.headAnchor) { 3323 delete newInst.headAnchor; 3324 } else { 3325 newInst.headAnchor = { x: 0, y: -70 }; 3326 } 3327 return { ...prev, [editingInstType]: { ...typeObj, [editingInstIndex]: newInst } }; 3328 }); 3329 }, 3330 className: `px-2 py-1 rounded text-[10px] font-bold transition-colors ${(activePreviewInst == null ? void 0 : activePreviewInst.headAnchor) ? "bg-emerald-600/30 text-emerald-400 border border-emerald-500/30" : "bg-zinc-800 text-zinc-500"}`, 3331 children: (activePreviewInst == null ? void 0 : activePreviewInst.headAnchor) ? "Locked (Green Dot)" : "Unlocked" 3332 } 3333 ) 3334 ] }) 3335 ] }),
3336 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-3 border-t border-zinc-800 pt-3 space-y-2", children: [ 3337 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-zinc-500 font-bold mb-2 uppercase tracking-wider text-[10px]", children: "Motion Physics & Rest Pose" }), 3338 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-2 bg-black/20 p-1.5 rounded", children: [ 3339 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 3340 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "block text-[8px] text-zinc-500 uppercase", children: "Left Hand rest (X,Y)" }), 3341 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1", children: [ 3342 /* @__PURE__ */ jsxRuntimeExports.jsx( 3343 "input", 3344 { 3345 type: "number", 3346 placeholder: "X", 3347 value: ((_g = (_f = activePreviewInst == null ? void 0 : activePreviewInst.restPose) == null ? void 0 : _f.leftHand) == null ? void 0 : _g.x) ?? "", 3348 onChange: (e) => { 3349 const val = parseFloat(e.target.value); 3350 setInstrumentConfigs((prev) => { 3351 const typeObj = prev[editingInstType]; 3352 const instObj = typeObj[editingInstIndex] || {}; 3353 const rp = instObj.restPose || {}; 3354 return { ...prev, [editingInstType]: { ...typeObj, [editingInstIndex]: { ...instObj, restPose: { ...rp, leftHand: isNaN(val) ? void 0 : { ...rp.leftHand || {}, x: val } } } } }; 3355 }); 3356 }, 3357 className: "w-1/2 bg-zinc-900 border border-zinc-700 rounded p-0.5 text-white text-[9px]" 3358 } 3359 ),
3360 /* @__PURE__ */ jsxRuntimeExports.jsx( 3361 "input", 3362 { 3363 type: "number", 3364 placeholder: "Y", 3365 value: ((_i = (_h = activePreviewInst == null ? void 0 : activePreviewInst.restPose) == null ? void 0 : _h.leftHand) == null ? void 0 : _i.y) ?? "", 3366 onChange: (e) => { 3367 const val = parseFloat(e.target.value); 3368 setInstrumentConfigs((prev) => { 3369 const typeObj = prev[editingInstType]; 3370 const instObj = typeObj[editingInstIndex] || {}; 3371 const rp = instObj.restPose || {}; 3372 return { ...prev, [editingInstType]: { ...typeObj, [editingInstIndex]: { ...instObj, restPose: { ...rp, leftHand: isNaN(val) ? void 0 : { ...rp.leftHand || {}, y: val } } } } }; 3373 }); 3374 }, 3375 className: "w-1/2 bg-zinc-900 border border-zinc-700 rounded p-0.5 text-white text-[9px]" 3376 } 3377 ) 3378 ] }) 3379 ] }),
3380 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 3381 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "block text-[8px] text-zinc-500 uppercase", children: "Right Hand rest (X,Y)" }), 3382 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1", children: [ 3383 /* @__PURE__ */ jsxRuntimeExports.jsx( 3384 "input", 3385 { 3386 type: "number", 3387 placeholder: "X", 3388 value: ((_k = (_j = activePreviewInst == null ? void 0 : activePreviewInst.restPose) == null ? void 0 : _j.rightHand) == null ? void 0 : _k.x) ?? "", 3389 onChange: (e) => { 3390 const val = parseFloat(e.target.value); 3391 setInstrumentConfigs((prev) => { 3392 const typeObj = prev[editingInstType]; 3393 const instObj = typeObj[editingInstIndex] || {}; 3394 const rp = instObj.restPose || {}; 3395 return { ...prev, [editingInstType]: { ...typeObj, [editingInstIndex]: { ...instObj, restPose: { ...rp, rightHand: isNaN(val) ? void 0 : { ...rp.rightHand || {}, x: val } } } } }; 3396 }); 3397 }, 3398 className: "w-1/2 bg-zinc-900 border border-zinc-700 rounded p-0.5 text-white text-[9px]" 3399 } 3400 ),
3401 /* @__PURE__ */ jsxRuntimeExports.jsx( 3402 "input", 3403 { 3404 type: "number", 3405 placeholder: "Y", 3406 value: ((_m = (_l = activePreviewInst == null ? void 0 : activePreviewInst.restPose) == null ? void 0 : _l.rightHand) == null ? void 0 : _m.y) ?? "", 3407 onChange: (e) => { 3408 const val = parseFloat(e.target.value); 3409 setInstrumentConfigs((prev) => { 3410 const typeObj = prev[editingInstType]; 3411 const instObj = typeObj[editingInstIndex] || {}; 3412 const rp = instObj.restPose || {}; 3413 return { ...prev, [editingInstType]: { ...typeObj, [editingInstIndex]: { ...instObj, restPose: { ...rp, rightHand: isNaN(val) ? void 0 : { ...rp.rightHand || {}, y: val } } } } }; 3414 }); 3415 }, 3416 className: "w-1/2 bg-zinc-900 border border-zinc-700 rounded p-0.5 text-white text-[9px]" 3417 } 3418 ) 3419 ] }) 3420 ] }) 3421 ] }),
3422 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-3 gap-1 bg-black/20 p-1.5 rounded", children: [ 3423 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 3424 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "block text-[8px] text-zinc-500 uppercase", children: "Min Note" }), 3425 /* @__PURE__ */ jsxRuntimeExports.jsx( 3426 "input", 3427 { 3428 type: "number", 3429 value: ((_n = activePreviewInst == null ? void 0 : activePreviewInst.noteRange) == null ? void 0 : _n.minNote) ?? "", 3430 onChange: (e) => { 3431 const val = parseInt(e.target.value); 3432 setInstrumentConfigs((prev) => { 3433 const typeObj = prev[editingInstType]; 3434 const instObj = typeObj[editingInstIndex] || {}; 3435 const nr = instObj.noteRange || {}; 3436 return { ...prev, [editingInstType]: { ...typeObj, [editingInstIndex]: { ...instObj, noteRange: { ...nr, minNote: isNaN(val) ? void 0 : val } } } }; 3437 }); 3438 }, 3439 className: "w-full bg-zinc-900 border border-zinc-700 rounded p-0.5 text-white text-[9px]" 3440 } 3441 ) 3442 ] }),
3443 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 3444 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "block text-[8px] text-zinc-500 uppercase", children: "Max Note" }), 3445 /* @__PURE__ */ jsxRuntimeExports.jsx( 3446 "input", 3447 { 3448 type: "number", 3449 value: ((_o = activePreviewInst == null ? void 0 : activePreviewInst.noteRange) == null ? void 0 : _o.maxNote) ?? "", 3450 onChange: (e) => { 3451 const val = parseInt(e.target.value); 3452 setInstrumentConfigs((prev) => { 3453 const typeObj = prev[editingInstType]; 3454 const instObj = typeObj[editingInstIndex] || {}; 3455 const nr = instObj.noteRange || {}; 3456 return { ...prev, [editingInstType]: { ...typeObj, [editingInstIndex]: { ...instObj, noteRange: { ...nr, maxNote: isNaN(val) ? void 0 : val } } } }; 3457 }); 3458 }, 3459 className: "w-full bg-zinc-900 border border-zinc-700 rounded p-0.5 text-white text-[9px]" 3460 } 3461 ) 3462 ] }),
3463 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 3464 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "block text-[8px] text-zinc-500 uppercase", children: "Width (px)" }), 3465 /* @__PURE__ */ jsxRuntimeExports.jsx( 3466 "input", 3467 { 3468 type: "number", 3469 value: ((_p = activePreviewInst == null ? void 0 : activePreviewInst.noteRange) == null ? void 0 : _p.physicalLength) ?? "", 3470 onChange: (e) => { 3471 const val = parseInt(e.target.value); 3472 setInstrumentConfigs((prev) => { 3473 const typeObj = prev[editingInstType]; 3474 const instObj = typeObj[editingInstIndex] || {}; 3475 const nr = instObj.noteRange || {}; 3476 return { ...prev, [editingInstType]: { ...typeObj, [editingInstIndex]: { ...instObj, noteRange: { ...nr, physicalLength: isNaN(val) ? void 0 : val } } } }; 3477 }); 3478 }, 3479 className: "w-full bg-zinc-900 border border-zinc-700 rounded p-0.5 text-white text-[9px]" 3480 } 3481 ) 3482 ] }) 3483 ] }),
3484 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1 bg-black/20 p-1.5 rounded", children: [ 3485 { key: "swayFrequency", label: "Sway Freq", defaultVal: 5e-3, min: 1e-3, max: 0.05, step: 1e-3 }, 3486 { key: "strikeFrequency", label: "Strike Freq", defaultVal: 0.02, min: 5e-3, max: 0.1, step: 5e-3 }, 3487 { key: "strikeAmplitude", label: "Strike Amp", defaultVal: 10, min: 0, max: 40, step: 1 }, 3488 { key: "bowPhaseStep", label: "Bow Step", defaultVal: 0.05, min: 0.01, max: 0.2, step: 0.01 }, 3489 { key: "bowGapMs", label: "Bow Gap (ms)", defaultVal: 500, min: 50, max: 2e3, step: 50 }, 3490 { key: "strumSpeedMult", label: "Strum Speed", defaultVal: 0.03, min: 5e-3, max: 0.15, step: 5e-3 }, 3491 { key: "strumVerticalAmp", label: "Strum Vert", defaultVal: 15, min: 0, max: 60, step: 1 }, 3492 { key: "strumHorizontalAmp", label: "Strum Horiz", defaultVal: 5, min: 0, max: 30, step: 1 }, 3493 { key: "ikLerpL", label: "IK Lerp L", defaultVal: 0.3, min: 0.01, max: 1, step: 0.01 }, 3494 { key: "ikLerpR", label: "IK Lerp R", defaultVal: 0.3, min: 0.01, max: 1, step: 0.01 } 3495 ].map(({ key, label, defaultVal, min, max, step }) => { 3496 var _a2; 3497 const currentVal = ((_a2 = activePreviewInst == null ? void 0 : activePreviewInst.motionPhysics) == null ? void 0 : _a2[key]) ?? defaultVal; 3498 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5", children: [
3499 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-zinc-400 text-xs w-24 shrink-0", children: label }), 3500 /* @__PURE__ */ jsxRuntimeExports.jsx( 3501 "input", 3502 { 3503 type: "range", 3504 min, 3505 max, 3506 step, 3507 value: currentVal, 3508 onChange: (e) => { 3509 const val = parseFloat(e.target.value); 3510 setInstrumentConfigs((prev) => { 3511 const typeObj = prev[editingInstType]; 3512 const instObj = typeObj[editingInstIndex] || {}; 3513 const mp = instObj.motionPhysics || {}; 3514 return { ...prev, [editingInstType]: { ...typeObj, [editingInstIndex]: { ...instObj, motionPhysics: { ...mp, [key]: val } } } }; 3515 }); 3516 }, 3517 className: "flex-1 accent-indigo-500 h-1" 3518 } 3519 ),
3520 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-zinc-300 text-xs w-10 text-right", children: currentVal }), 3521 /* @__PURE__ */ jsxRuntimeExports.jsx( 3522 "button", 3523 { 3524 onClick: () => { 3525 setInstrumentConfigs((prev) => { 3526 const typeObj = prev[editingInstType]; 3527 const instObj = typeObj[editingInstIndex] || {}; 3528 const mp = { ...instObj.motionPhysics || {} }; 3529 delete mp[key]; 3530 return { ...prev, [editingInstType]: { ...typeObj, [editingInstIndex]: { ...instObj, motionPhysics: Object.keys(mp).length > 0 ? mp : void 0 } } }; 3531 }); 3532 }, 3533 className: "text-zinc-500 hover:text-zinc-300 text-xs", 3534 children: "âº" 3535 } 3536 ) 3537 ] }, key); 3538 }) }) 3539 ] }) 3540 ] }), 3541 (activeTab === "instruments" || activeTab === "characters" && previewInstKey) && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "col-span-2 mt-3 border-t border-zinc-800 pt-3 space-y-2", children: [
3542 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [ 3543 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-zinc-400 font-bold uppercase tracking-wider text-xs", children: "Instrument Playing Actions" }), 3544 /* @__PURE__ */ jsxRuntimeExports.jsxs( 3545 "button", 3546 { 3547 onClick: () => { 3548 setInstrumentConfigs((prev) => { 3549 const typeObj = prev[editingInstType]; 3550 const instObj = typeObj[editingInstIndex] || {}; 3551 const actions = instObj.actions || []; 3552 const newAction = { 3553 id: `inst_${editingInstType}_${editingInstIndex}_${Date.now()}`, 3554 name: "New Instrument Action", 3555 category: "instrument", 3556 trigger: "playing", 3557 priority: 60, 3558 duration: 2e3, 3559 tempoSync: true, 3560 loop: true, 3561 blendInMs: 300, 3562 blendOutMs: 300, 3563 channels: [], 3564 affectedJoints: [] 3565 }; 3566 return { ...prev, [editingInstType]: { ...typeObj, [editingInstIndex]: { ...instObj, actions: [...actions, newAction] } } }; 3567 }); 3568 }, 3569 className: "text-indigo-400 hover:text-indigo-300 text-[10px] flex items-center gap-0.5", 3570 children: [
3571 /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { size: 10 }), 3572 " Add" 3573 ] 3574 } 3575 ) 3576 ] }), 3577 ((activePreviewInst == null ? void 0 : activePreviewInst.actions) || []).length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-zinc-500 text-xs italic", children: "No instrument-specific actions. Uses body actions from the Actions tab." }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1.5", children: ((activePreviewInst == null ? void 0 : activePreviewInst.actions) || []).map((action, aIdx) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-900 rounded border border-zinc-800 p-2 space-y-1.5", children: [ 3578 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [ 3579 /* @__PURE__ */ jsxRuntimeExports.jsx( 3580 "input", 3581 { 3582 value: action.name, 3583 onChange: (e) => { 3584 setInstrumentConfigs((prev) => { 3585 const typeObj = prev[editingInstType]; 3586 const instObj = typeObj[editingInstIndex] || {}; 3587 const actions = [...instObj.actions || []]; 3588 actions[aIdx] = { ...actions[aIdx], name: e.target.value }; 3589 return { ...prev, [editingInstType]: { ...typeObj, [editingInstIndex]: { ...instObj, actions } } }; 3590 }); 3591 }, 3592 className: "bg-transparent text-zinc-200 text-[10px] font-bold border-b border-zinc-700 flex-1 focus:outline-none focus:border-indigo-500" 3593 } 3594 ),
3595 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 ml-2", children: [ 3596 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-[8px] px-1 rounded ${action.trigger === "playing" ? "bg-blue-900/50 text-blue-300" : "bg-amber-900/50 text-amber-300"}`, children: action.trigger }), 3597 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[8px] text-zinc-600", children: [ 3598 action.channels.length, 3599 "ch" 3600 ] }), 3601 /* @__PURE__ */ jsxRuntimeExports.jsx( 3602 "button", 3603 { 3604 onClick: () => { 3605 setInstrumentConfigs((prev) => { 3606 const typeObj = prev[editingInstType]; 3607 const instObj = typeObj[editingInstIndex] || {}; 3608 const actions = (instObj.actions || []).filter((_, i) => i !== aIdx); 3609 return { ...prev, [editingInstType]: { ...typeObj, [editingInstIndex]: { ...instObj, actions } } }; 3610 }); 3611 }, 3612 className: "text-zinc-600 hover:text-red-400", 3613 children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 10 }) 3614 } 3615 ) 3616 ] }) 3617 ] }),
3618 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-[9px]", children: [ 3619 /* @__PURE__ */ jsxRuntimeExports.jsxs( 3620 "select", 3621 { 3622 value: action.trigger, 3623 onChange: (e) => { 3624 setInstrumentConfigs((prev) => { 3625 const typeObj = prev[editingInstType]; 3626 const instObj = typeObj[editingInstIndex] || {}; 3627 const actions = [...instObj.actions || []]; 3628 actions[aIdx] = { ...actions[aIdx], trigger: e.target.value }; 3629 return { ...prev, [editingInstType]: { ...typeObj, [editingInstIndex]: { ...instObj, actions } } }; 3630 }); 3631 }, 3632 className: "bg-zinc-800 border border-zinc-700 text-zinc-300 rounded px-1 py-0.5", 3633 children: [ 3634 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "playing", children: "Playing" }),
3635 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "idle", children: "Idle" }), 3636 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "note_on", children: "Note On" }), 3637 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "always", children: "Always" }) 3638 ] 3639 } 3640 ), 3641 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-600", children: "P:" }), 3642 /* @__PURE__ */ jsxRuntimeExports.jsx( 3643 "input", 3644 { 3645 type: "number", 3646 value: action.priority, 3647 onChange: (e) => { 3648 setInstrumentConfigs((prev) => { 3649 const typeObj = prev[editingInstType]; 3650 const instObj = typeObj[editingInstIndex] || {}; 3651 const actions = [...instObj.actions || []]; 3652 actions[aIdx] = { ...actions[aIdx], priority: parseInt(e.target.value) || 0 }; 3653 return { ...prev, [editingInstType]: { ...typeObj, [editingInstIndex]: { ...instObj, actions } } }; 3654 }); 3655 }, 3656 className: "w-10 bg-zinc-800 border border-zinc-700 text-zinc-300 rounded px-1 py-0.5" 3657 } 3658 ),
3659 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-1 text-zinc-400 cursor-pointer", children: [ 3660 /* @__PURE__ */ jsxRuntimeExports.jsx( 3661 "input", 3662 { 3663 type: "checkbox", 3664 checked: action.loop, 3665 onChange: (e) => { 3666 setInstrumentConfigs((prev) => { 3667 const typeObj = prev[editingInstType]; 3668 const instObj = typeObj[editingInstIndex] || {}; 3669 const actions = [...instObj.actions || []]; 3670 actions[aIdx] = { ...actions[aIdx], loop: e.target.checked }; 3671 return { ...prev, [editingInstType]: { ...typeObj, [editingInstIndex]: { ...instObj, actions } } }; 3672 }); 3673 }, 3674 className: "accent-indigo-500" 3675 } 3676 ), 3677 "Loop" 3678 ] }),
3679 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-1 text-zinc-400 cursor-pointer", children: [ 3680 /* @__PURE__ */ jsxRuntimeExports.jsx( 3681 "input", 3682 { 3683 type: "checkbox", 3684 checked: action.tempoSync, 3685 onChange: (e) => { 3686 setInstrumentConfigs((prev) => { 3687 const typeObj = prev[editingInstType]; 3688 const instObj = typeObj[editingInstIndex] || {}; 3689 const actions = [...instObj.actions || []]; 3690 actions[aIdx] = { ...actions[aIdx], tempoSync: e.target.checked }; 3691 return { ...prev, [editingInstType]: { ...typeObj, [editingInstIndex]: { ...instObj, actions } } }; 3692 }); 3693 }, 3694 className: "accent-indigo-500" 3695 } 3696 ), 3697 "Sync" 3698 ] }) 3699 ] }),
3700 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1", children: [ 3701 action.channels.map((ch, cIdx) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 text-[8px] bg-black/30 rounded px-1.5 py-1", children: [ 3702 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `px-1 rounded ${ch.type === "wave" ? "bg-blue-900/50 text-blue-300" : "bg-purple-900/50 text-purple-300"}`, children: ch.type }), 3703 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-zinc-300", children: [ 3704 ch.joint, 3705 ".", 3706 ch.property 3707 ] }), 3708 ch.type === "wave" && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-zinc-600", children: [ 3709 "f=", 3710 ch.frequency, 3711 " a=", 3712 ch.amplitude 3713 ] }), 3714 ch.type === "keyframe" && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-zinc-600", children: [ 3715 (ch.keyframes || []).length, 3716 " kf" 3717 ] }),
3718 /* @__PURE__ */ jsxRuntimeExports.jsx( 3719 "button", 3720 { 3721 onClick: () => { 3722 setInstrumentConfigs((prev) => { 3723 const typeObj = prev[editingInstType]; 3724 const instObj = typeObj[editingInstIndex] || {}; 3725 const actions = [...instObj.actions || []]; 3726 actions[aIdx] = { ...actions[aIdx], channels: actions[aIdx].channels.filter((_, i) => i !== cIdx) }; 3727 return { ...prev, [editingInstType]: { ...typeObj, [editingInstIndex]: { ...instObj, actions } } }; 3728 }); 3729 }, 3730 className: "text-zinc-600 hover:text-red-400 ml-auto", 3731 children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 8 }) 3732 } 3733 ) 3734 ] }, cIdx)), 3735 /* @__PURE__ */ jsxRuntimeExports.jsxs( 3736 "button", 3737 { 3738 onClick: () => { 3739 const newCh = { joint: "body", property: "y", type: "wave", waveform: "sine", frequency: 1, amplitude: 5, amplitudeBase: 0, intensityScale: 1, phase: 0, offset: 0 }; 3740 setInstrumentConfigs((prev) => { 3741 const typeObj = prev[editingInstType]; 3742 const instObj = typeObj[editingInstIndex] || {}; 3743 const actions = [...instObj.actions || []]; 3744 actions[aIdx] = { ...actions[aIdx], channels: [...actions[aIdx].channels, newCh] }; 3745 return { ...prev, [editingInstType]: { ...typeObj, [editingInstIndex]: { ...instObj, actions } } }; 3746 }); 3747 }, 3748 className: "text-indigo-400 hover:text-indigo-300 text-[8px] flex items-center gap-0.5", 3749 children: [
3750 /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { size: 8 }), 3751 " Add Channel" 3752 ] 3753 } 3754 ) 3755 ] }) 3756 ] }, action.id || aIdx)) }) 3757 ] }), 3758 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "col-span-2 space-y-1 mt-2", children: [ 3759 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-zinc-500 font-bold mb-1 uppercase tracking-wider", children: "Interactive Edit Mode" }), 3760 /* @__PURE__ */ jsxRuntimeExports.jsxs( 3761 "select", 3762 { 3763 value: editMode, 3764 onChange: (e) => setEditMode(e.target.value), 3765 className: "w-full bg-zinc-900 border border-zinc-700 rounded p-1 text-white", 3766 children: [ 3767 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "none", children: "None (View Only)" }), 3768 activeTab === "characters" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
3769 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "anchor", children: "Reposition Anchor (X, Y)" }), 3770 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "shoulder_l", children: "Reposition Left Shoulder (IK Base)" }), 3771 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "shoulder_r", children: "Reposition Right Shoulder (IK Base)" }), 3772 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "socket_mouth", children: "Place Mouth Socket Pin" }), 3773 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "socket_shoulder_l", children: "Place Left Shoulder Socket Pin" }), 3774 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "socket_shoulder_r", children: "Place Right Shoulder Socket Pin" }), 3775 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "socket_elbow_l", children: "Place Left Elbow Socket Pin" }), 3776 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "socket_elbow_r", children: "Place Right Elbow Socket Pin" }),
3777 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "socket_hand_l", children: "Place Left Hand Socket Pin" }), 3778 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "socket_hand_r", children: "Place Right Hand Socket Pin" }) 3779 ] }), 3780 (activeTab === "instruments" || activeTab === "characters" && previewInstKey) && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ 3781 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "head_anchor", children: "Edit Head Anchor (Mouthpiece Pos)" }), 3782 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "plug_mouthpiece", children: "Place Mouthpiece Plug Pin" }), 3783 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "plug_hand_l", children: "Place Left Hand Grip Plug Pin" }), 3784 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "plug_hand_r", children: "Place Right Hand Grip Plug Pin" }),
3785 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "path_l_start", children: "Edit Left Hand (Path Start / Rest)" }), 3786 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "path_l_end", children: "Edit Left Hand (Path End)" }), 3787 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "path_r_start", children: "Edit Right Hand (Path Start / Rest)" }), 3788 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "path_r_end", children: "Edit Right Hand (Path End)" }) 3789 ] }), 3790 activeTab === "clothing" && activePreviewClothing && /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: Object.keys(activePreviewClothing.parts).map((part) => { 3791 var _a2; 3792 const glue = (_a2 = activePreviewClothing.gluing) == null ? void 0 : _a2[part]; 3793 if (!glue || glue.type === "none") return null; 3794 return /* @__PURE__ */ jsxRuntimeExports.jsxs(React.Fragment, { children: [
3795 /* @__PURE__ */ jsxRuntimeExports.jsxs("option", { value: `clothing_plug_p1_${part}`, children: [ 3796 "Place ", 3797 part, 3798 " Plug P1 Pin" 3799 ] }), 3800 glue.type === "two-point" && /* @__PURE__ */ jsxRuntimeExports.jsxs("option", { value: `clothing_plug_p2_${part}`, children: [ 3801 "Place ", 3802 part, 3803 " Plug P2 Pin" 3804 ] }) 3805 ] }, part); 3806 }) }) 3807 ] 3808 } 3809 ), 3810 editMode !== "none" && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-emerald-400 mt-1", children: "Click anywhere in the preview below to set coordinates." }), 3811 (() => { 3812 var _a2, _b2, _c2, _d2, _e2, _f2, _g2, _h2, _i2, _j2; 3813 if (editMode === "none") return null; 3814 let label = "Active Coordinates"; 3815 let currentX = 0; 3816 let currentY = 0; 3817 let updateCoords = () => { 3818 }; 3819 if (activeTab === "characters" && selectedConfig) { 3820 if (editMode === "anchor") { 3821 label = "Anchor Position"; 3822 currentX = selectedConfig.anchorX ?? 0; 3823 currentY = selectedConfig.anchorY ?? 0; 3824 updateCoords = (x, y) => handleConfigChange(selectedConfig.id, { anchorX: x, anchorY: y }); 3825 } else if (editMode === "shoulder_l") { 3826 label = "Left Shoulder (IK Base)"; 3827 currentX = ((_a2 = selectedConfig.ikLeftArm) == null ? void 0 : _a2.originX) ?? -30; 3828 currentY = ((_b2 = selectedConfig.ikLeftArm) == null ? void 0 : _b2.originY) ?? -20; 3829 updateCoords = (x, y) => { 3830 handleIkChange(selectedConfig.id, "ikLeftArm", "originX", x); 3831 handleIkChange(selectedConfig.id, "ikLeftArm", "originY", y); 3832 }; 3833 } else if (editMode === "shoulder_r") { 3834 label = "Right Shoulder (IK Base)"; 3835 currentX = ((_c2 = selectedConfig.ikRightArm) == null ? void 0 : _c2.originX) ?? 30; 3836 currentY = ((_d2 = selectedConfig.ikRightArm) == null ? void 0 : _d2.originY) ?? -20; 3837 updateCoords = (x, y) => { 3838 handleIkChange(selectedConfig.id, "ikRightArm", "originX", x); 3839 handleIkChange(selectedConfig.id, "ikRightArm", "originY", y); 3840 }; 3841 } else if (editMode.startsWith("socket_")) { 3842 const socketName = editMode.replace("socket_", ""); 3843 label = `${socketName.replace("_", " ")} Socket`; 3844 const svgCoords = parseSvgSocketsAndPlugs(selectedConfig.svgContent); 3845 const svgVal = svgCoords[`socket_${socketName}`]; 3846 const configVal = (_e2 = selectedConfig.sockets) == null ? void 0 : _e2[socketName]; 3847 const val = configVal || svgVal || { x: 0, y: 0 }; 3848 currentX = val.x; 3849 currentY = val.y; 3850 updateCoords = (x, y) => { 3851 const currentSockets = selectedConfig.sockets || {}; 3852 handleConfigChange(selectedConfig.id, { 3853 sockets: { 3854 ...currentSockets, 3855 [socketName]: { x, y } 3856 } 3857 }); 3858 }; 3859 } 3860 } else if ((activeTab === "instruments" || activeTab === "characters" && previewInstKey) && editingInstType && editingInstIndex !== void 0) { 3861 const typeObj = instrumentConfigs[editingInstType]; 3862 const instObj = (typeObj == null ? void 0 : typeObj[editingInstIndex]) || {}; 3863 if (editMode === "head_anchor") { 3864 label = "Head Anchor"; 3865 currentX = ((_f2 = instObj.headAnchor) == null ? void 0 : _f2.x) ?? 0; 3866 currentY = ((_g2 = instObj.headAnchor) == null ? void 0 : _g2.y) ?? 0; 3867 updateCoords = (x, y) => { 3868 setInstrumentConfigs((prev) => { 3869 const tObj = prev[editingInstType]; 3870 const iObj = tObj[editingInstIndex] || {}; 3871 return { 3872 ...prev, 3873 [editingInstType]: { 3874 ...tObj, 3875 [editingInstIndex]: { ...iObj, headAnchor: { x, y } } 3876 } 3877 }; 3878 }); 3879 }; 3880 } else if (editMode.startsWith("plug_")) { 3881 const plugName = editMode.replace("plug_", ""); 3882 label = `${plugName.replace("_", " ")} Plug`; 3883 const plugVal = ((_h2 = instObj.plugs) == null ? void 0 : _h2[plugName]) || { x: 0, y: 0 }; 3884 currentX = plugVal.x; 3885 currentY = plugVal.y; 3886 updateCoords = (x, y) => { 3887 setInstrumentConfigs((prev) => { 3888 const tObj = prev[editingInstType]; 3889 const iObj = tObj[editingInstIndex] || {}; 3890 const currentPlugs = iObj.plugs || {}; 3891 return { 3892 ...prev, 3893 [editingInstType]: { 3894 ...tObj, 3895 [editingInstIndex]: { 3896 ...iObj, 3897 plugs: { 3898 ...currentPlugs, 3899 [plugName]: { ...currentPlugs[plugName] || {}, x, y } 3900 } 3901 } 3902 } 3903 }; 3904 }); 3905 }; 3906 } else if (editMode.startsWith("path_")) { 3907 const isLeft = editMode.includes("_l_"); 3908 const isStart = editMode.endsWith("_start"); 3909 const handKey = isLeft ? "leftHandMotion" : "rightHandMotion"; 3910 const pointKey = isStart ? "start" : "end"; 3911 label = `${isLeft ? "Left" : "Right"} Hand Path ${isStart ? "Start" : "End"}`; 3912 const motionObj = instObj[handKey] || { logic: "none", start: { x: 0, y: 0 }, end: { x: 0, y: 0 } }; 3913 currentX = ((_i2 = motionObj[pointKey]) == null ? void 0 : _i2.x) ?? 0; 3914 currentY = ((_j2 = motionObj[pointKey]) == null ? void 0 : _j2.y) ?? 0; 3915 updateCoords = (x, y) => { 3916 setInstrumentConfigs((prev) => { 3917 const tObj = prev[editingInstType]; 3918 const iObj = tObj[editingInstIndex] || {}; 3919 const mObj = iObj[handKey] || { logic: "none", start: { x: 0, y: 0 }, end: { x: 0, y: 0 } }; 3920 const updatedMotion = { 3921 ...mObj, 3922 [pointKey]: { x, y } 3923 }; 3924 let newRestPose = iObj.restPose || {}; 3925 if (isStart) { 3926 newRestPose = { 3927 ...newRestPose, 3928 [isLeft ? "leftHand" : "rightHand"]: { x, y } 3929 }; 3930 } 3931 return { 3932 ...prev, 3933 [editingInstType]: { 3934 ...tObj, 3935 [editingInstIndex]: { 3936 ...iObj, 3937 [handKey]: updatedMotion, 3938 restPose: newRestPose 3939 } 3940 } 3941 }; 3942 }); 3943 }; 3944 } 3945 } else if (activeTab === "clothing" && selectedClothingDef) { 3946 if (editMode.startsWith("clothing_plug_")) { 3947 const parts = editMode.split("_"); 3948 const pointKey = parts[2]; 3949 const partName = parts.slice(3).join("_"); 3950 label = `${partName.replace("_", " ")} Plug (${pointKey.toUpperCase()})`;
3951 const currentPlugs = selectedClothingDef.plugs || {}; 3952 const partPlugs = currentPlugs[partName] || {}; 3953 const val = partPlugs[pointKey] || { x: 0, y: 0 }; 3954 currentX = val.x; 3955 currentY = val.y; 3956 updateCoords = (x, y) => { 3957 setClothingConfigs((prev) => ({ 3958 ...prev, 3959 [selectedClothingDef.id]: { 3960 ...selectedClothingDef, 3961 plugs: { 3962 ...currentPlugs, 3963 [partName]: { 3964 ...partPlugs, 3965 [pointKey]: { x, y } 3966 } 3967 } 3968 } 3969 })); 3970 }; 3971 } 3972 } 3973 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2 bg-emerald-950/20 border border-emerald-500/30 rounded p-2 text-xs space-y-1.5 animate-fadeIn", children: [
3974 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "font-bold text-emerald-400 flex items-center justify-between", children: [ 3975 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [ 3976 label, 3977 " Coordinates" 3978 ] }), 3979 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-zinc-500 font-mono", children: "Precision Adjust" }) 3980 ] }), 3981 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-2", children: [ 3982 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 3983 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[9px] text-zinc-500 uppercase font-semibold", children: "X Position" }), 3984 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [ 3985 /* @__PURE__ */ jsxRuntimeExports.jsx( 3986 "button", 3987 { 3988 type: "button", 3989 onClick: () => updateCoords(currentX - 1, currentY), 3990 className: "px-1.5 py-0.5 bg-zinc-800 border border-zinc-700 hover:bg-zinc-700 rounded text-zinc-300 text-[10px] font-bold", 3991 children: "-" 3992 } 3993 ),
3994 /* @__PURE__ */ jsxRuntimeExports.jsx( 3995 "input", 3996 { 3997 type: "number", 3998 value: currentX, 3999 onChange: (e) => updateCoords(parseFloat(e.target.value) || 0, currentY), 4000 className: "w-full bg-zinc-900 border border-zinc-700 rounded px-1 py-0.5 text-white font-mono text-center text-xs" 4001 } 4002 ), 4003 /* @__PURE__ */ jsxRuntimeExports.jsx( 4004 "button", 4005 { 4006 type: "button", 4007 onClick: () => updateCoords(currentX + 1, currentY), 4008 className: "px-1.5 py-0.5 bg-zinc-800 border border-zinc-700 hover:bg-zinc-700 rounded text-zinc-300 text-[10px] font-bold", 4009 children: "+" 4010 } 4011 ) 4012 ] }) 4013 ] }),
4014 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 4015 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[9px] text-zinc-500 uppercase font-semibold", children: "Y Position" }), 4016 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [ 4017 /* @__PURE__ */ jsxRuntimeExports.jsx( 4018 "button", 4019 { 4020 type: "button", 4021 onClick: () => updateCoords(currentX, currentY - 1), 4022 className: "px-1.5 py-0.5 bg-zinc-800 border border-zinc-700 hover:bg-zinc-700 rounded text-zinc-300 text-[10px] font-bold", 4023 children: "-" 4024 } 4025 ), 4026 /* @__PURE__ */ jsxRuntimeExports.jsx( 4027 "input", 4028 { 4029 type: "number", 4030 value: currentY, 4031 onChange: (e) => updateCoords(currentX, parseFloat(e.target.value) || 0), 4032 className: "w-full bg-zinc-900 border border-zinc-700 rounded px-1 py-0.5 text-white font-mono text-center text-xs" 4033 } 4034 ),
4035 /* @__PURE__ */ jsxRuntimeExports.jsx( 4036 "button", 4037 { 4038 type: "button", 4039 onClick: () => updateCoords(currentX, currentY + 1), 4040 className: "px-1.5 py-0.5 bg-zinc-800 border border-zinc-700 hover:bg-zinc-700 rounded text-zinc-300 text-[10px] font-bold", 4041 children: "+" 4042 } 4043 ) 4044 ] }) 4045 ] }) 4046 ] }) 4047 ] }); 4048 })() 4049 ] }) 4050 ] }) 4051 ] }), 4052 activeTab === "characters" && selectedConfig && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "max-w-4xl space-y-8", children: [ 4053 (() => { 4054 const svgCoords = parseSvgSocketsAndPlugs(selectedConfig.svgContent); 4055 const expected = ["mouth", "shoulder_l", "shoulder_r", "elbow_l", "elbow_r", "hand_l", "hand_r"]; 4056 const missing = expected.filter((name) => { 4057 var _a2; 4058 const hasInSvg = svgCoords[`socket_${name}`] !== void 0; 4059 const hasInConfig = ((_a2 = selectedConfig.sockets) == null ? void 0 : _a2[name]) !== void 0; 4060 return !hasInSvg && !hasInConfig; 4061 }); 4062 if (missing.length === 0) return null; 4063 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-amber-950/40 border border-amber-800 text-amber-200 p-3 rounded-lg flex flex-col gap-1 text-xs", children: [
4064 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "font-bold flex items-center gap-1.5 text-amber-400", children: [ 4065 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm", children: "â ï¸" }), 4066 " Missing Character Sockets" 4067 ] }), 4068 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-zinc-400 font-normal", children: "The following sockets are missing from both the SVG file and database overrides. Click the ð¯ Pin button next to a socket below to calibrate it on the canvas." }), 4069 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-wrap gap-1.5 mt-1.5", children: missing.map((w) => /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "px-2 py-0.5 bg-amber-900/40 border border-amber-700/50 rounded text-amber-300 font-mono font-semibold", children: [ 4070 "socket_", 4071 w 4072 ] }, w)) }) 4073 ] }); 4074 })(), 4075 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-2 gap-6", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
4076 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 4077 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-500 uppercase tracking-wider mb-2", children: "Identifier (Key)" }), 4078 /* @__PURE__ */ jsxRuntimeExports.jsx( 4079 "input", 4080 { 4081 type: "text", 4082 value: selectedConfig.id, 4083 onChange: (e) => { 4084 const newId = e.target.value; 4085 const newConfigs = { ...configs }; 4086 newConfigs[newId] = { ...selectedConfig, id: newId }; 4087 delete newConfigs[selectedId]; 4088 setConfigs(newConfigs); 4089 setSelectedId(newId); 4090 }, 4091 className: "w-full bg-zinc-900 border border-zinc-700 rounded p-2 text-white font-mono text-sm" 4092 } 4093 ) 4094 ] }), 4095 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
4096 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-500 uppercase tracking-wider mb-2", children: "Display Name" }), 4097 /* @__PURE__ */ jsxRuntimeExports.jsx( 4098 "input", 4099 { 4100 type: "text", 4101 value: selectedConfig.name, 4102 onChange: (e) => handleConfigChange(selectedConfig.id, { name: e.target.value }), 4103 className: "w-full bg-zinc-900 border border-zinc-700 rounded p-2 text-white" 4104 } 4105 ) 4106 ] }), 4107 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 4108 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-500 uppercase tracking-wider mb-2", children: "Animation Family" }), 4109 /* @__PURE__ */ jsxRuntimeExports.jsxs( 4110 "select", 4111 { 4112 value: selectedConfig.family, 4113 onChange: (e) => handleConfigChange(selectedConfig.id, { family: e.target.value }), 4114 className: "w-full bg-zinc-900 border border-zinc-700 rounded p-2 text-white", 4115 children: [
4116 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "percussion", children: "Percussion" }), 4117 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "keys", children: "Keys" }), 4118 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "strings", children: "Strings" }), 4119 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "winds", children: "Winds" }), 4120 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "other", children: "Other / Generic" }) 4121 ] 4122 } 4123 ) 4124 ] }), 4125 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [ 4126 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 4127 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "block text-xs font-bold text-zinc-500 uppercase tracking-wider mb-2", children: [ 4128 "Scale factor ", 4129 isInherited("scale") && /* @__P
4129URE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-indigo-400 lowercase italic normal-case ml-1", children: "(gorilla default)" }) 4130 ] }), 4131 /* @__PURE__ */ jsxRuntimeExports.jsx( 4132 "input", 4133 { 4134 type: "number", 4135 step: "0.1", 4136 value: selectedConfig.scale, 4137 onChange: (e) => handleConfigChange(selectedConfig.id, { scale: parseFloat(e.target.value) || 1 }), 4138 className: "w-full bg-zinc-900 border border-zinc-700 rounded p-2 text-white" 4139 } 4140 ) 4141 ] }), 4142 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-2", children: [ 4143 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 4144 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "block text-xs font-bold text-zinc-500 uppercase tracking-wider mb-2", children: [ 4145 "Anchor X ",
4146 isInherited("anchorX") && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-indigo-400 lowercase italic normal-case ml-1", children: "(gorilla default)" }) 4147 ] }), 4148 /* @__PURE__ */ jsxRuntimeExports.jsx( 4149 "input", 4150 { 4151 type: "number", 4152 value: selectedConfig.anchorX, 4153 onChange: (e) => handleConfigChange(selectedConfig.id, { anchorX: parseInt(e.target.value) || 0 }), 4154 className: "w-full bg-zinc-900 border border-zinc-700 rounded p-2 text-white" 4155 } 4156 ) 4157 ] }), 4158 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 4159 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "block text-xs font-bold text-zinc-500 uppercase tracking-wider mb-2", children: [ 4160 "Anchor Y ", 4161 isInherited("anchorY") && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-indigo-400 lowercase italic normal-case ml-1", children: "(gorilla default)" }) 4162 ] }),
4163 /* @__PURE__ */ jsxRuntimeExports.jsx( 4164 "input", 4165 { 4166 type: "number", 4167 value: selectedConfig.anchorY, 4168 onChange: (e) => handleConfigChange(selectedConfig.id, { anchorY: parseInt(e.target.value) || 0 }), 4169 className: "w-full bg-zinc-900 border border-zinc-700 rounded p-2 text-white" 4170 } 4171 ) 4172 ] }) 4173 ] }), 4174 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-2 mt-4", children: [ 4175 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 4176 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "block text-xs font-bold text-zinc-500 uppercase tracking-wider mb-2", children: [ 4177 "Instrument Scale ", 4178 isInherited("instrumentScale") && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-indigo-400 lowercase italic normal-case ml-1", children: "(gorilla default)" }) 4179 ] }),
4180 /* @__PURE__ */ jsxRuntimeExports.jsx( 4181 "input", 4182 { 4183 type: "number", 4184 step: "0.05", 4185 value: selectedConfig.instrumentScale ?? 1, 4186 onChange: (e) => handleConfigChange(selectedConfig.id, { instrumentScale: parseFloat(e.target.value) || 1 }), 4187 className: "w-full bg-zinc-900 border border-zinc-700 rounded p-2 text-white" 4188 } 4189 ) 4190 ] }), 4191 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 4192 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "block text-xs font-bold text-zinc-500 uppercase tracking-wider mb-2", children: [ 4193 "Instrument Rotation ", 4194 isInherited("instrumentRotation") && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-indigo-400 lowercase italic normal-case ml-1", children: "(gorilla default)" }) 4195 ] }),
4196 /* @__PURE__ */ jsxRuntimeExports.jsx( 4197 "input", 4198 { 4199 type: "number", 4200 value: selectedConfig.instrumentRotation ?? 0, 4201 onChange: (e) => handleConfigChange(selectedConfig.id, { instrumentRotation: parseInt(e.target.value) || 0 }), 4202 className: "w-full bg-zinc-900 border border-zinc-700 rounded p-2 text-white" 4203 } 4204 ) 4205 ] }) 4206 ] }) 4207 ] }), 4208 " " 4209 ] }) }), 4210 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-indigo-900/50 rounded-lg p-4 bg-indigo-900/10", children: [ 4211 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "flex items-center gap-2 text-sm font-bold text-indigo-400 uppercase tracking-wider mb-4", children: "2-Bone Inverse Kinematics Overrides" }), 4212 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-8", children: [
4213 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [ 4214 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs font-bold text-zinc-400 border-b border-zinc-800 pb-1", children: "Left Arm" }), 4215 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-2", children: [ 4216 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 4217 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "block text-[10px] uppercase text-zinc-500", children: [ 4218 "Upper Length ", 4219 isInherited("ikLeftArm", "upperArmLength") && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] text-indigo-400 lowercase italic block", children: "(gorilla default)" }) 4220 ] }), 4221 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "number", value: ((_q = selectedConfig.ikLeftArm) == null ? void 0 : _q.upperArmLength) || 40, onChange: (e) => handleIkChange(selectedConfig.id, "ikLeftArm", "upperArmLength", parseInt(e.target.value)), className: "w-full bg-zinc-900 border border-zinc-700 rounded p-1 text-white text-sm" }) 4222 ] }),
4223 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 4224 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "block text-[10px] uppercase text-zinc-500", children: [ 4225 "Lower Length ", 4226 isInherited("ikLeftArm", "lowerArmLength") && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] text-indigo-400 lowercase italic block", children: "(gorilla default)" }) 4227 ] }), 4228 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "number", value: ((_r = selectedConfig.ikLeftArm) == null ? void 0 : _r.lowerArmLength) || 40, onChange: (e) => handleIkChange(selectedConfig.id, "ikLeftArm", "lowerArmLength", parseInt(e.target.value)), className: "w-full bg-zinc-900 border border-zinc-700 rounded p-1 text-white text-sm" }) 4229 ] }), 4230 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "col-span-2 mt-2", children: [ 4231 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "block text-[10px] uppercase text-zinc-500 mb-1", children: [ 4232 "Bend Direction ", 4233 isInherited("ikLeftArm", "bendDirection") && /* @__P
4233URE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] text-indigo-400 lowercase italic ml-1", children: "(gorilla default)" }) 4234 ] }), 4235 /* @__PURE__ */ jsxRuntimeExports.jsxs("select", { value: ((_s = selectedConfig.ikLeftArm) == null ? void 0 : _s.bendDirection) || 1, onChange: (e) => handleIkChange(selectedConfig.id, "ikLeftArm", "bendDirection", parseInt(e.target.value)), className: "w-full bg-zinc-900 border border-zinc-700 rounded p-1 text-white text-sm", children: [ 4236 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: 1, children: "Natural (1)" }), 4237 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: -1, children: "Inverted (-1)" }) 4238 ] }) 4239 ] }) 4240 ] }) 4241 ] }), 4242 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [ 4243 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs font-bold text-zinc-400 border-b border-zinc-800 pb-1", children: "Right Arm" }),
4244 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-2", children: [ 4245 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 4246 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "block text-[10px] uppercase text-zinc-500", children: [ 4247 "Upper Length ", 4248 isInherited("ikRightArm", "upperArmLength") && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] text-indigo-400 lowercase italic block", children: "(gorilla default)" }) 4249 ] }), 4250 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "number", value: ((_t = selectedConfig.ikRightArm) == null ? void 0 : _t.upperArmLength) || 40, onChange: (e) => handleIkChange(selectedConfig.id, "ikRightArm", "upperArmLength", parseInt(e.target.value)), className: "w-full bg-zinc-900 border border-zinc-700 rounded p-1 text-white text-sm" }) 4251 ] }), 4252 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
4253 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "block text-[10px] uppercase text-zinc-500", children: [ 4254 "Lower Length ", 4255 isInherited("ikRightArm", "lowerArmLength") && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] text-indigo-400 lowercase italic block", children: "(gorilla default)" }) 4256 ] }), 4257 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "number", value: ((_u = selectedConfig.ikRightArm) == null ? void 0 : _u.lowerArmLength) || 40, onChange: (e) => handleIkChange(selectedConfig.id, "ikRightArm", "lowerArmLength", parseInt(e.target.value)), className: "w-full bg-zinc-900 border border-zinc-700 rounded p-1 text-white text-sm" }) 4258 ] }), 4259 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "col-span-2 mt-2", children: [ 4260 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "block text-[10px] uppercase text-zinc-500 mb-1", children: [ 4261 "Bend Direction ", 4262 isInherited("ikRightArm", "bendDirection") && /* @__P
4262URE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] text-indigo-400 lowercase italic ml-1", children: "(gorilla default)" }) 4263 ] }), 4264 /* @__PURE__ */ jsxRuntimeExports.jsxs("select", { value: ((_v = selectedConfig.ikRightArm) == null ? void 0 : _v.bendDirection) || -1, onChange: (e) => handleIkChange(selectedConfig.id, "ikRightArm", "bendDirection", parseInt(e.target.value)), className: "w-full bg-zinc-900 border border-zinc-700 rounded p-1 text-white text-sm", children: [ 4265 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: 1, children: "Natural (1)" }), 4266 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: -1, children: "Inverted (-1)" }) 4267 ] }) 4268 ] }) 4269 ] }) 4270 ] }) 4271 ] }) 4272 ] }), 4273 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-zinc-800 rounded-lg p-4 bg-zinc-900/50", children: [ 4274 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-xs font-bold text-indigo-400 uppercase tracking-wider mb-2", children: [
4275 /* @__PURE__ */ jsxRuntimeExports.jsx(PenLine, { size: 16 }), 4276 " Character Sockets / Reference Joints" 4277 ] }), 4278 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-500 mb-3", children: "Define skeletal socket positions for attachment alignment. Click ð¯ next to a socket to click-position it on the preview canvas." }), 4279 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-2 max-h-60 overflow-y-auto custom-scrollbar pr-1", children: ["mouth", "shoulder_l", "shoulder_r", "elbow_l", "elbow_r", "hand_l", "hand_r"].map((socketName) => { 4280 var _a2, _b2, _c2; 4281 const svgCoords = parseSvgSocketsAndPlugs(selectedConfig.svgContent); 4282 const svgVal = svgCoords[`socket_${socketName}`]; 4283 const configVal = (_a2 = selectedConfig.sockets) == null ? void 0 : _a2[socketName]; 4284 const isSvgOnly = svgVal && !configVal; 4285 const displayX = (configVal == null ? void 0 : configVal.x) ?? (svgVal == null ? void 0 : svgVal.x) ?? ""; 4286 const displayY = (configVal == null ? void 0 : configVal.y) ?? (svgVal == null ? void 0 : svgVal.y) ?? ""; 4287 const rawSocketVal = (_c2 = (_b2 = configs[selectedConfig.id]) == null ? void 0 : _b2.sockets) == null ? void 0 : _c2[socketName]; 4288 const isGorillaDefault = selectedConfig.id !== "percussion" && !rawSocketVal && configVal; 4289 const isEditingThis = editMode === `socket_${socketName}`; 4290 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `bg-black/40 border rounded p-2 grid grid-cols-12 gap-2 items-center ${isEditingThis ? "border-emerald-500 bg-emerald-950/10" : "border-zinc-800"}`, children: [
4291 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "col-span-3 text-xs font-bold text-zinc-400 truncate flex items-center gap-1", children: [ 4292 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: socketName }), 4293 isSvgOnly && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] bg-zinc-800 text-zinc-400 px-1 rounded", children: "SVG" }), 4294 isGorillaDefault && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] bg-indigo-950/80 text-indigo-300 border border-indigo-700/50 px-1 rounded", title: "Inherited from Gorilla default", children: "Gorilla Default" }), 4295 !svgVal && !configVal && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] bg-red-900/40 text-red-400 px-1 rounded", children: "Missing" }) 4296 ] }), 4297 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "col-span-3", children: [ 4298 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[8px] text-zinc-500 uppercase", children: "Parent Joint" }),
4299 /* @__PURE__ */ jsxRuntimeExports.jsxs( 4300 "select", 4301 { 4302 disabled: true, 4303 value: socketName === "mouth" || socketName === "head" ? "head" : "body", 4304 className: "w-full bg-zinc-950 border border-zinc-800 rounded p-1 text-zinc-400 text-[10px] cursor-not-allowed opacity-80", 4305 children: [ 4306 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "head", children: "head" }), 4307 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "body", children: "body" }) 4308 ] 4309 } 4310 ) 4311 ] }), 4312 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "col-span-2", children: [ 4313 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[8px] text-zinc-500 uppercase", children: "X Coord" }), 4314 /* @__PURE__ */ jsxRuntimeExports.jsx( 4315 "input", 4316 { 4317 type: "number", 4318 value: displayX, 4319 placeholder: svgVal ? `SVG: ${svgVal.x}` : "None", 4320 onChange: (e) => { 4321 const val = parseFloat(e.target.value); 4322 const currentSockets = selectedConfig.sockets || {}; 4323 handleConfigChange(selectedConfig.id, { 4324 sockets: { 4325 ...currentSockets, 4326 [socketName]: { x: isNaN(val) ? 0 : val, y: (configVal == null ? void 0 : configVal.y) ?? (svgVal == null ? void 0 : svgVal.y) ?? 0 } 4327 } 4328 }); 4329 }, 4330 className: "w-full bg-zinc-900 border border-zinc-700 rounded p-1 text-white text-[10px]" 4331 } 4332 ) 4333 ] }),
4334 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "col-span-2", children: [ 4335 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[8px] text-zinc-500 uppercase", children: "Y Coord" }), 4336 /* @__PURE__ */ jsxRuntimeExports.jsx( 4337 "input", 4338 { 4339 type: "number", 4340 value: displayY, 4341 placeholder: svgVal ? `SVG: ${svgVal.y}` : "None", 4342 onChange: (e) => { 4343 const val = parseFloat(e.target.value); 4344 const currentSockets = selectedConfig.sockets || {}; 4345 handleConfigChange(selectedConfig.id, { 4346 sockets: { 4347 ...currentSockets, 4348 [socketName]: { x: (configVal == null ? void 0 : configVal.x) ?? (svgVal == null ? void 0 : svgVal.x) ?? 0, y: isNaN(val) ? 0 : val } 4349 } 4350 }); 4351 }, 4352 className: "w-full bg-zinc-900 border border-zinc-700 rounded p-1 text-white text-[10px]" 4353 } 4354 ) 4355 ] }),
4356 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "col-span-2 flex justify-end", children: /* @__PURE__ */ jsxRuntimeExports.jsx( 4357 "button", 4358 { 4359 type: "button", 4360 onClick: () => setEditMode(isEditingThis ? "none" : `socket_${socketName}`), 4361 className: `p-1.5 rounded border transition-colors ${isEditingThis ? "bg-emerald-600 border-emerald-500 text-white" : "bg-zinc-800 border-zinc-700 text-zinc-400 hover:bg-zinc-750 hover:text-white"}`, 4362 title: "Pin on Canvas", 4363 children: "ð¯" 4364 } 4365 ) }) 4366 ] }, socketName); 4367 }) }) 4368 ] }), 4369 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-zinc-800 rounded-lg p-4 bg-zinc-900/50", children: [ 4370 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-xs font-bold text-indigo-400 uppercase tracking-wider mb-2", children: [
4371 /* @__PURE__ */ jsxRuntimeExports.jsx(Layers, { size: 16 }), 4372 " Character Body Parts (SVGs & Transforms)" 4373 ] }), 4374 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-500 mb-3", children: "Customize individual body part SVGs and adjust translations, scales, and rotation for character parts." }), 4375 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-4 max-h-[500px] overflow-y-auto custom-scrollbar pr-1 mb-4", children: ["head", "body", "upper_arm_l", "lower_arm_l", "hand_l", "upper_arm_r", "lower_arm_r", "hand_r", "leg_l", "foot_l", "leg_r", "foot_r"].map((part) => { 4376 var _a2; 4377 const isExpanded = !!expandedCharacterParts[part]; 4378 const isEmpty = !selectedConfig || !selectedConfig.parts || !selectedConfig.parts[part]; 4379 const isSelected = selectedPartId === part; 4380 const t = ((_a2 = selectedConfig.transforms) == null ? void 0 : _a2[part]) || { x: 0, y: 0, scaleX: 1, scaleY: 1, rotation: 0 }; 4381 return /* @__PURE__ */ jsxRuntimeExports.jsxs( 4382 "div", 4383 { 4384 onClick: () => setSelectedPartId(part), 4385 className: `transition-all duration-200 border rounded p-3 flex flex-col gap-2 cursor-pointer ${isSelected ? "bg-indigo-950/20 border-indigo-500 ring-1 ring-indigo-500/50 shadow-[0_0_8px_rgba(99,102,241,0.2)]" : "bg-black border border-zinc-800 hover:border-zinc-700"}`, 4386 children: [
4387 /* @__PURE__ */ jsxRuntimeExports.jsxs( 4388 "div", 4389 { 4390 className: "flex items-center justify-between cursor-pointer select-none", 4391 onClick: (e) => { 4392 setSelectedPartId(part); 4393 setExpandedCharacterParts((prev) => ({ ...prev, [part]: !prev[part] })); 4394 }, 4395 children: [ 4396 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 4397 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-500 text-[10px] w-3 inline-block", children: isExpanded ? "â¼" : "â¶" }), 4398 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: `block text-xs uppercase tracking-wider cursor-pointer ${isEmpty ? "text-zinc-500 opacity-60" : "font-bold text-indigo-400"}`, children: part }) 4399 ] }),
4400 /* @__PURE__ */ jsxRuntimeExports.jsxs( 4401 "label", 4402 { 4403 onClick: (e) => e.stopPropagation(), 4404 className: "cursor-pointer flex items-center gap-1 text-[10px] px-2 py-0.5 bg-zinc-850 hover:bg-zinc-800 border border-zinc-700 text-zinc-300 rounded transition-colors", 4405 children: [ 4406 /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { size: 10 }), 4407 " Upload SVG", 4408 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "file", accept: ".svg", className: "hidden", onChange: (e) => { 4409 var _a3; 4410 pushUndo(); 4411 const file = (_a3 = e.target.files) == null ? void 0 : _a3[0]; 4412 if (!file) return; 4413 const reader = new FileReader(); 4414 reader.onload = (event) => { 4415 var _a4; 4416 try { 4417 const content = (_a4 = event.target) == null ? void 0 : _a4.result; 4418 if (!content.includes("<svg") || !content.includes("</svg>")) { 4419 alert("Invalid SVG file format"); 4420 return; 4421 } 4422 const match = content.match(/<svg[^>]*>([\s\S]*?)<\/svg>/i); 4423 const svgInner = match ? match[1] : content; 4424 const currentParts = selectedConfig.parts || {}; 4425 handleConfigChange(selectedConfig.id, { 4426 parts: { 4427 ...currentParts, 4428 [part]: svgInner 4429 } 4430 }); 4431 } catch (err) { 4432 alert("Failed to parse SVG file"); 4433 } 4434 }; 4435 reader.readAsText(file); 4436 } }) 4437 ] 4438 } 4439 ) 4440 ] 4441 } 4442 ),
4443 isExpanded && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { onClick: (e) => e.stopPropagation(), className: "bg-zinc-900 border border-zinc-800 p-2.5 rounded flex flex-col gap-2 animate-fadeIn cursor-default mt-1", children: [ 4444 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-5 gap-2", children: [ 4445 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 4446 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] text-zinc-500 uppercase", children: "Scale X" }), 4447 /* @__PURE__ */ jsxRuntimeExports.jsx( 4448 "input", 4449 { 4450 type: "number", 4451 step: "0.05", 4452 value: t.scaleX ?? 1, 4453 onChange: (e) => { 4454 pushUndo(); 4455 const val = parseFloat(e.target.value); 4456 handleConfigChange(selectedConfig.id, { 4457 transforms: { 4458 ...selectedConfig.transforms, 4459 [part]: { ...t, scaleX: isNaN(val) ? 1 : val } 4460 } 4461 }); 4462 }, 4463 className: "w-full bg-black border border-zinc-700 rounded p-1 text-white text-[10px]" 4464 } 4465 ) 4466 ] }),
4467 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 4468 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] text-zinc-500 uppercase", children: "Scale Y" }), 4469 /* @__PURE__ */ jsxRuntimeExports.jsx( 4470 "input", 4471 { 4472 type: "number", 4473 step: "0.05", 4474 value: t.scaleY ?? 1, 4475 onChange: (e) => { 4476 pushUndo(); 4477 const val = parseFloat(e.target.value); 4478 handleConfigChange(selectedConfig.id, { 4479 transforms: { 4480 ...selectedConfig.transforms, 4481 [part]: { ...t, scaleY: isNaN(val) ? 1 : val } 4482 } 4483 }); 4484 }, 4485 className: "w-full bg-black border border-zinc-700 rounded p-1 text-white text-[10px]" 4486 } 4487 ) 4488 ] }),
4489 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 4490 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] text-zinc-500 uppercase", children: "Offset X" }), 4491 /* @__PURE__ */ jsxRuntimeExports.jsx( 4492 "input", 4493 { 4494 type: "number", 4495 value: t.x ?? 0, 4496 onChange: (e) => { 4497 pushUndo(); 4498 const val = parseFloat(e.target.value) || 0; 4499 handleConfigChange(selectedConfig.id, { 4500 transforms: { 4501 ...selectedConfig.transforms, 4502 [part]: { ...t, x: val } 4503 } 4504 }); 4505 }, 4506 className: "w-full bg-black border border-zinc-700 rounded p-1 text-white text-[10px]" 4507 } 4508 ) 4509 ] }),
4510 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 4511 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] text-zinc-500 uppercase", children: "Offset Y" }), 4512 /* @__PURE__ */ jsxRuntimeExports.jsx( 4513 "input", 4514 { 4515 type: "number", 4516 value: t.y ?? 0, 4517 onChange: (e) => { 4518 pushUndo(); 4519 const val = parseFloat(e.target.value) || 0; 4520 handleConfigChange(selectedConfig.id, { 4521 transforms: { 4522 ...selectedConfig.transforms, 4523 [part]: { ...t, y: val } 4524 } 4525 }); 4526 }, 4527 className: "w-full bg-black border border-zinc-700 rounded p-1 text-white text-[10px]" 4528 } 4529 ) 4530 ] }),
4531 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 4532 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] text-zinc-500 uppercase", children: "Rot (deg)" }), 4533 /* @__PURE__ */ jsxRuntimeExports.jsx( 4534 "input", 4535 { 4536 type: "number", 4537 value: t.rotation ?? 0, 4538 onChange: (e) => { 4539 pushUndo(); 4540 const val = parseFloat(e.target.value) || 0; 4541 handleConfigChange(selectedConfig.id, { 4542 transforms: { 4543 ...selectedConfig.transforms, 4544 [part]: { ...t, rotation: val } 4545 } 4546 }); 4547 }, 4548 className: "w-full bg-black border border-zinc-700 rounded p-1 text-white text-[10px]" 4549 } 4550 ) 4551 ] }) 4552 ] }), 4553 !isEmpty && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mt-2 border-t border-zinc-800 pt-2", children: [
4554 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-emerald-400", children: "â Custom SVG loaded" }), 4555 /* @__PURE__ */ jsxRuntimeExports.jsx( 4556 "button", 4557 { 4558 onClick: (e) => { 4559 e.stopPropagation(); 4560 pushUndo(); 4561 const currentParts = { ...selectedConfig.parts || {} }; 4562 delete currentParts[part]; 4563 handleConfigChange(selectedConfig.id, { parts: currentParts }); 4564 }, 4565 className: "text-red-400 hover:text-red-300 text-[10px]", 4566 children: "Remove SVG" 4567 } 4568 ) 4569 ] }) 4570 ] }) 4571 ] 4572 }, 4573 part 4574 ); 4575 }) }) 4576 ] }),
4577 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-zinc-800 rounded-lg p-4 bg-zinc-900/50", children: [ 4578 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-xs font-bold text-indigo-400 uppercase tracking-wider mb-2", children: [ 4579 /* @__PURE__ */ jsxRuntimeExports.jsx(Layers, { size: 16 }), 4580 " Layer Rendering Order (Z)" 4581 ] }), 4582 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-500 mb-3", children: "Override the default rendering depth (zIndex) for character body parts and clothing layers." }), 4583 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-2 max-h-60 overflow-y-auto custom-scrollbar pr-1", children: Object.entries({ 4584 leg_l: 1, 4585 clothing_leg_l: 1.5, 4586 leg_r: 2, 4587 clothing_leg_r: 2.5, 4588 foot_l: 3, 4589 foot_r: 4, 4590 body: 5, 4591 clothing_body: 5.5, 4592 head: 6, 4593 clothing_head: 6.5, 4594 upper_arm_r: 7, 4595 clothing_upper_arm_r: 7.2, 4596 lower_arm_r: 8, 4597 clothing_lower_arm_r: 8.2, 4598 hand_r: 9, 4599 clothing_hand_r: 9.2, 4600 instrument: 10, 4601 upper_arm_l: 11, 4602 clothing_upper_arm_l: 11.2, 4603 lower_arm_l: 12, 4604 clothing_lower_arm_l: 12.2, 4605 hand_l: 13, 4606 clothing_hand_l: 13.2 4607 }).map(([part, defaultZ]) => { 4608 var _a2, _b2; 4609 const currentZ = ((_a2 = selectedConfig.baseZOrder) == null ? void 0 : _a2[part]) ?? defaultZ; 4610 const isOverridden = ((_b2 = selectedConfig.baseZOrder) == null ? void 0 : _b2[part]) !== void 0; 4611 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-black/40 border border-zinc-800 rounded p-2 flex items-center justify-between gap-4", children: [
4612 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs font-bold text-zinc-400 truncate flex items-center gap-1.5", children: [ 4613 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: part }), 4614 !isOverridden && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[8px] bg-zinc-800/60 text-zinc-500 px-1 rounded uppercase font-semibold", children: "default" }) 4615 ] }), 4616 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 4617 /* @__PURE__ */ jsxRuntimeExports.jsx( 4618 "input", 4619 { 4620 type: "number", 4621 step: "0.1", 4622 value: currentZ, 4623 onChange: (e) => { 4624 const val = parseFloat(e.target.value); 4625 const baseZOrder = selectedConfig.baseZOrder || {}; 4626 handleConfigChange(selectedConfig.id, { 4627 baseZOrder: { 4628 ...baseZOrder, 4629 [part]: isNaN(val) ? defaultZ : val 4630 } 4631 }); 4632 }, 4633 className: "w-16 bg-zinc-900 border border-zinc-700 rounded p-1 text-white text-[10px] text-right font-mono" 4634 } 4635 ), 4636 isOverridden && /* @__PURE__ */ jsxRuntimeExports.jsx( 4637 "button", 4638 { 4639 onClick: () => { 4640 const baseZOrder = { ...selectedConfig.baseZOrder || {} }; 4641 delete baseZOrder[part]; 4642 handleConfigChange(selectedConfig.id, { 4643 baseZOrder: Object.keys(baseZOrder).length > 0 ? baseZOrder : void 0 4644 }); 4645 }, 4646 title: "Reset override", 4647 className: "text-zinc-650 hover:text-zinc-400 text-[10px] font-bold", 4648 children: "âº" 4649 } 4650 ) 4651 ] }) 4652 ] }, part); 4653 }) }) 4654 ] }),
4655 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-zinc-800 rounded-lg p-4 bg-zinc-900/50", children: [ 4656 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between mb-3", children: [ 4657 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-xs font-bold text-zinc-500 uppercase tracking-wider", children: [ 4658 /* @__PURE__ */ jsxRuntimeExports.jsx(Image$1, { size: 16 }), 4659 " SVG Source Code" 4660 ] }), 4661 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "cursor-pointer flex items-center gap-1 text-xs px-3 py-1 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 rounded transition-colors", children: [ 4662 /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { size: 14 }), 4663 " Upload SVG", 4664 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "file", accept: ".svg", className: "hidden", onChange: (e) => handleSvgUpload(e, selectedConfig.id) }) 4665 ] }) }) 4666 ] }), 4667 uploadError && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-red-500 text-xs mb-2", children: uploadError }),
4668 /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-zinc-500 mb-2", children: [ 4669 "Must contain GSAP targets: ", 4670 /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-indigo-400", children: 'id="head"' }), 4671 ", ", 4672 /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-indigo-400", children: 'id="body"' }), 4673 ", ", 4674 /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-indigo-400", children: 'id="hand_l"' }), 4675 ", ", 4676 /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-indigo-400", children: 'id="hand_r"' }), 4677 "." 4678 ] }), 4679 /* @__PURE__ */ jsxRuntimeExports.jsx( 4680 "textarea", 4681 { 4682 value: selectedConfig.svgContent, 4683 onChange: (e) => handleConfigChange(selectedConfig.id, { svgContent: e.target.value }), 4684 className: "w-full h-48 bg-black border border-zinc-700 rounded p-3 text-emerald-400 font-mono text-sm resize-y" 4685 } 4686 ) 4687 ] }),
4688 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-zinc-800 rounded-lg p-4 bg-zinc-900/50", children: [ 4689 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "flex items-center gap-2 text-xs font-bold text-zinc-500 uppercase tracking-wider mb-3", children: "IK Note Mapping (JSON)" }), 4690 /* @__PURE__ */ jsxRuntimeExports.jsx( 4691 "textarea", 4692 { 4693 defaultValue: JSON.stringify(selectedConfig.noteMap, null, 2), 4694 onChange: (e) => handleJsonUpdate(selectedConfig.id, e.target.value), 4695 className: `w-full h-48 bg-black border ${jsonError ? "border-red-500" : "border-zinc-700"} rounded p-3 text-amber-400 font-mono text-sm resize-y` 4696 } 4697 ), 4698 jsonError && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-red-500 text-xs mt-2", children: jsonError }) 4699 ] }) 4700 ] }), 4701 activeTab === "instruments" && selectedInstKey && (() => { 4702 var _a2, _b2, _c2, _d2, _e2, _f2, _g2, _h2, _i2, _j2, _k2; 4703 const isAccessory = selectedPartId === "accessory"; 4704 const activeSvgContent = isAccessory ? typeof selectedInstSvg === "object" ? selectedInstSvg == null ? void 0 : selectedInstSvg.accessorySvg : "" : typeof selectedInstSvg === "string" ? selectedInstSvg : selectedInstSvg == null ? void 0 : selectedInstSvg.svgContent; 4705 const activeTransform = isAccessory ? typeof selectedInstSvg === "object" ? selectedInstSvg == null ? void 0 : selectedInstSvg.accessoryTransform : void 0 : typeof selectedInstSvg === "object" ? selectedInstSvg == null ? void 0 : selectedInstSvg.transform : void 0; 4706 const svgCoords = parseSvgSocketsAndPlugs(typeof selectedInstSvg === "string" ? selectedInstSvg : (selectedInstSvg == null ? void 0 : selectedInstSvg.svgContent) || ""); 4707 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "max-w-4xl space-y-8", children: [
4708 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [ 4709 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-sm font-bold text-white border-b border-zinc-800 pb-2", children: "Instrument Parts" }), 4710 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [ 4711 /* @__PURE__ */ jsxRuntimeExports.jsxs( 4712 "div", 4713 { 4714 onClick: () => setSelectedPartId("instrument"), 4715 className: `transition-all duration-200 border rounded p-3 flex flex-col gap-1 cursor-pointer ${selectedPartId === "instrument" || selectedPartId !== "accessory" ? "bg-indigo-950/20 border-indigo-500 ring-1 ring-indigo-500/50 shadow-[0_0_8px_rgba(99,102,241,0.2)]" : "bg-black border border-zinc-800 hover:border-zinc-700"}`, 4716 children: [ 4717 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs uppercase tracking-wider font-bold text-indigo-400", children: "Main Instrument" }),
4718 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-zinc-500", children: "Root / Body Mounted" }) 4719 ] 4720 } 4721 ), 4722 /* @__PURE__ */ jsxRuntimeExports.jsxs( 4723 "div", 4724 { 4725 onClick: () => setSelectedPartId("accessory"), 4726 className: `transition-all duration-200 border rounded p-3 flex flex-col gap-1 cursor-pointer ${selectedPartId === "accessory" ? "bg-indigo-950/20 border-indigo-500 ring-1 ring-indigo-500/50 shadow-[0_0_8px_rgba(99,102,241,0.2)]" : "bg-black border border-zinc-800 hover:border-zinc-700"}`, 4727 children: [ 4728 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs uppercase tracking-wider font-bold text-indigo-400", children: "Accessory" }), 4729 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-zinc-500", children: "e.g. Bow, Drumstick" }) 4730 ] 4731 } 4732 ) 4733 ] }) 4734 ] }),
4735 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-zinc-800 rounded-lg p-4 bg-zinc-900/50", children: [ 4736 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between mb-3", children: [ 4737 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-xs font-bold text-zinc-500 uppercase tracking-wider", children: [ 4738 /* @__PURE__ */ jsxRuntimeExports.jsx(Image$1, { size: 16 }), 4739 " ", 4740 isAccessory ? "Accessory" : "Instrument", 4741 " SVG Graphic (", 4742 getInstrumentLabel(selectedInstType, selectedInstIndex.toString()), 4743 ")" 4744 ] }), 4745 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "cursor-pointer flex items-center gap-1 text-xs px-3 py-1 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 rounded transition-colors", children: [
4746 /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { size: 14 }), 4747 " Upload SVG", 4748 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "file", accept: ".svg", className: "hidden", onChange: handleInstSvgUpload }) 4749 ] }) }) 4750 ] }), 4751 uploadError && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-red-500 text-xs mb-2", children: uploadError }), 4752 /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-zinc-500 mb-2", children: [ 4753 "Must contain an ", 4754 /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-indigo-400", children: 'id="instrument"' }), 4755 " root group tag." 4756 ] }), 4757 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mb-4 bg-[#0a0a0a] border border-zinc-700 rounded p-4 flex items-center justify-center h-[300px] relative", children: activeSvgContent ? /* @__PURE__ */ jsxRuntimeExports.jsxs( 4758 "svg", 4759 { 4760 width: "100%", 4761 height: "100%",
4762 viewBox: "-200 -200 400 400", 4763 className: "overflow-visible", 4764 children: [ 4765 /* @__PURE__ */ jsxRuntimeExports.jsx( 4766 "g", 4767 { 4768 transform: activeTransform ? `translate(${activeTransform.x || 0}, ${activeTransform.y || 0}) scale(${activeTransform.scaleX ?? activeTransform.scale ?? 1}, ${activeTransform.scaleY ?? activeTransform.scale ?? 1}) rotate(${activeTransform.rotation || 0})` : "", 4769 dangerouslySetInnerHTML: { __html: activeSvgContent } 4770 } 4771 ), 4772 /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "0", cy: "0", r: "3", fill: "#ef4444", opacity: "0.5" }), 4773 /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M-15,0 L15,0 M0,-15 L0,15", stroke: "#ef4444", strokeWidth: "1", opacity: "0.5" }) 4774 ] 4775 } 4776 ) : /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-zinc-600 text-sm", children: [ 4777 "No SVG data available for ", 4778 isAccessory ? "Accessory" : "Main Instrument" 4779 ] }) }),
4780 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [ 4781 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-5 gap-2 bg-zinc-900 border border-zinc-700 p-3 rounded", children: [ 4782 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 4783 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] text-zinc-400 uppercase font-bold", children: "Scale X" }), 4784 /* @__PURE__ */ jsxRuntimeExports.jsx( 4785 "input", 4786 { 4787 type: "number", 4788 step: "0.1", 4789 value: (activeTransform == null ? void 0 : activeTransform.scaleX) ?? (activeTransform == null ? void 0 : activeTransform.scale) ?? 1, 4790 onChange: (e) => { 4791 const val = parseFloat(e.target.value); 4792 setInstrumentConfigs((prev) => { 4793 const typeObj = prev[selectedInstType]; 4794 const instObj = typeObj[selectedInstIndex]; 4795 const currentDef = typeof instObj === "string" ? { svgContent: instObj, mountPoint: "body" } : instObj; 4796 const transformField = isAccessory ? "accessoryTransform" : "transform"; 4797 const currentTransform = currentDef[transformField] || { x: 0, y: 0, scaleX: 1, scaleY: 1, rotation: 0 }; 4798 return { 4799 ...prev, 4800 [selectedInstType]: { 4801 ...typeObj, 4802 [selectedInstIndex]: { 4803 ...currentDef, 4804 [transformField]: { 4805 ...currentTransform, 4806 scaleX: isNaN(val) ? 1 : val 4807 } 4808 } 4809 } 4810 }; 4811 }); 4812 }, 4813 className: "w-full bg-black border border-zinc-700 rounded p-1 text-white text-sm" 4814 } 4815 ) 4816 ] }),
4817 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 4818 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] text-zinc-400 uppercase font-bold", children: "Scale Y" }), 4819 /* @__PURE__ */ jsxRuntimeExports.jsx( 4820 "input", 4821 { 4822 type: "number", 4823 step: "0.1", 4824 value: (activeTransform == null ? void 0 : activeTransform.scaleY) ?? (activeTransform == null ? void 0 : activeTransform.scale) ?? 1, 4825 onChange: (e) => { 4826 const val = parseFloat(e.target.value); 4827 setInstrumentConfigs((prev) => { 4828 const typeObj = prev[selectedInstType]; 4829 const instObj = typeObj[selectedInstIndex]; 4830 const currentDef = typeof instObj === "string" ? { svgContent: instObj, mountPoint: "body" } : instObj; 4831 const transformField = isAccessory ? "accessoryTransform" : "transform"; 4832 const currentTransform = currentDef[transformField] || { x: 0, y: 0, scaleX: 1, scaleY: 1, rotation: 0 }; 4833 return { 4834 ...prev, 4835 [selectedInstType]: { 4836 ...typeObj, 4837 [selectedInstIndex]: { 4838 ...currentDef, 4839 [transformField]: { 4840 ...currentTransform, 4841 scaleY: isNaN(val) ? 1 : val 4842 } 4843 } 4844 } 4845 }; 4846 }); 4847 }, 4848 className: "w-full bg-black border border-zinc-700 rounded p-1 text-white text-sm" 4849 } 4850 ) 4851 ] }),
4852 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 4853 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] text-zinc-400 uppercase font-bold", children: "Rotation (deg)" }), 4854 /* @__PURE__ */ jsxRuntimeExports.jsx( 4855 "input", 4856 { 4857 type: "number", 4858 value: (activeTransform == null ? void 0 : activeTransform.rotation) ?? 0, 4859 onChange: (e) => { 4860 const val = parseFloat(e.target.value) || 0; 4861 setInstrumentConfigs((prev) => { 4862 const typeObj = prev[selectedInstType]; 4863 const instObj = typeObj[selectedInstIndex]; 4864 const currentDef = typeof instObj === "string" ? { svgContent: instObj, mountPoint: "body" } : instObj; 4865 const transformField = isAccessory ? "accessoryTransform" : "transform"; 4866 const currentTransform = currentDef[transformField] || { x: 0, y: 0, scaleX: 1, scaleY: 1, rotation: 0 }; 4867 return { 4868 ...prev, 4869 [selectedInstType]: { 4870 ...typeObj, 4871 [selectedInstIndex]: { 4872 ...currentDef, 4873 [transformField]: { 4874 ...currentTransform, 4875 rotation: val 4876 } 4877 } 4878 } 4879 }; 4880 }); 4881 }, 4882 className: "w-full bg-black border border-zinc-700 rounded p-1 text-white text-sm" 4883 } 4884 ) 4885 ] }),
4886 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 4887 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] text-zinc-400 uppercase font-bold", children: "Offset X" }), 4888 /* @__PURE__ */ jsxRuntimeExports.jsx( 4889 "input", 4890 { 4891 type: "number", 4892 value: (activeTransform == null ? void 0 : activeTransform.x) ?? 0, 4893 onChange: (e) => { 4894 const val = parseFloat(e.target.value) || 0; 4895 setInstrumentConfigs((prev) => { 4896 const typeObj = prev[selectedInstType]; 4897 const instObj = typeObj[selectedInstIndex]; 4898 const currentDef = typeof instObj === "string" ? { svgContent: instObj, mountPoint: "body" } : instObj; 4899 const transformField = isAccessory ? "accessoryTransform" : "transform"; 4900 const currentTransform = currentDef[transformField] || { x: 0, y: 0, scaleX: 1, scaleY: 1, rotation: 0 }; 4901 return { 4902 ...prev, 4903 [selectedInstType]: { 4904 ...typeObj, 4905 [selectedInstIndex]: { 4906 ...currentDef, 4907 [transformField]: { 4908 ...currentTransform, 4909 x: val 4910 } 4911 } 4912 } 4913 }; 4914 }); 4915 }, 4916 className: "w-full bg-black border border-zinc-700 rounded p-1 text-white text-sm" 4917 } 4918 ) 4919 ] }),
4920 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 4921 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] text-zinc-400 uppercase font-bold", children: "Offset Y" }), 4922 /* @__PURE__ */ jsxRuntimeExports.jsx( 4923 "input", 4924 { 4925 type: "number", 4926 value: (activeTransform == null ? void 0 : activeTransform.y) ?? 0, 4927 onChange: (e) => { 4928 const val = parseFloat(e.target.value) || 0; 4929 setInstrumentConfigs((prev) => { 4930 const typeObj = prev[selectedInstType]; 4931 const instObj = typeObj[selectedInstIndex]; 4932 const currentDef = typeof instObj === "string" ? { svgContent: instObj, mountPoint: "body" } : instObj; 4933 const transformField = isAccessory ? "accessoryTransform" : "transform"; 4934 const currentTransform = currentDef[transformField] || { x: 0, y: 0, scaleX: 1, scaleY: 1, rotation: 0 }; 4935 return { 4936 ...prev, 4937 [selectedInstType]: { 4938 ...typeObj, 4939 [selectedInstIndex]: { 4940 ...currentDef, 4941 [transformField]: { 4942 ...currentTransform, 4943 y: val 4944 } 4945 } 4946 } 4947 }; 4948 }); 4949 }, 4950 className: "w-full bg-black border border-zinc-700 rounded p-1 text-white text-sm" 4951 } 4952 ) 4953 ] }) 4954 ] }), 4955 isAccessory && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-900 border border-zinc-700 p-3 rounded", children: [
4956 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-400 uppercase mb-1", children: "Accessory Mount Point" }), 4957 /* @__PURE__ */ jsxRuntimeExports.jsx( 4958 "select", 4959 { 4960 value: (typeof selectedInstSvg === "object" ? selectedInstSvg == null ? void 0 : selectedInstSvg.accessoryMountPoint : "") || "hand_r", 4961 onChange: (e) => { 4962 const val = e.target.value; 4963 setInstrumentConfigs((prev) => { 4964 const typeObj = prev[selectedInstType]; 4965 const instObj = typeObj[selectedInstIndex]; 4966 const currentDef = typeof instObj === "string" ? { svgContent: instObj, mountPoint: "body" } : instObj; 4967 return { 4968 ...prev, 4969 [selectedInstType]: { 4970 ...typeObj, 4971 [selectedInstIndex]: { 4972 ...currentDef, 4973 accessoryMountPoint: val 4974 } 4975 } 4976 }; 4977 }); 4978 }, 4979 className: "w-full bg-black border border-zinc-700 rounded p-2 text-white text-sm", 4980 children: ["root", "body", "hand_l", "hand_r", "front", "head"].map((opt) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: opt, children: opt }, opt)) 4981 } 4982 ) 4983 ] }), 4984 !isAccessory && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-900 border border-zinc-700 p-3 rounded", children: [
4985 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-400 uppercase mb-1", children: "Main Instrument Mount Point" }), 4986 /* @__PURE__ */ jsxRuntimeExports.jsx( 4987 "select", 4988 { 4989 value: (typeof selectedInstSvg === "object" ? selectedInstSvg == null ? void 0 : selectedInstSvg.mountPoint : "") || "body", 4990 onChange: (e) => { 4991 const val = e.target.value; 4992 setInstrumentConfigs((prev) => { 4993 const typeObj = prev[selectedInstType]; 4994 const instObj = typeObj[selectedInstIndex]; 4995 const currentDef = typeof instObj === "string" ? { svgContent: instObj, mountPoint: "body" } : instObj; 4996 return { 4997 ...prev, 4998 [selectedInstType]: { 4999 ...typeObj, 5000 [selectedInstIndex]: { 5001 ...currentDef, 5002 mountPoint: val 5003 } 5004 } 5005 }; 5006 }); 5007 }, 5008 className: "w-full bg-black border border-zinc-700 rounded p-2 text-white text-sm", 5009 children: ["root", "body", "hand_l", "hand_r", "front", "head"].map((opt) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: opt, children: opt }, opt)) 5010 } 5011 ) 5012 ] }) 5013 ] }) 5014 ] }), 5015 selectedInstSvg && typeof selectedInstSvg !== "object" ? null : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
5016 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-900 border border-zinc-700 p-3 rounded space-y-3", children: [ 5017 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs font-bold text-indigo-400 uppercase tracking-wider", children: "Gluing & Alignment" }), 5018 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-500", children: "Bind instrument plugs to character body parts. You can add multiple gluing points. The first entry is usually the primary anchor." }), 5019 (() => { 5020 const instObj = selectedInstSvg; 5021 let gluingArray = Array.isArray(instObj.gluing) ? instObj.gluing : instObj.gluing ? [instObj.gluing] : []; 5022 if (gluingArray.length === 0 && instObj.headAnchor) { 5023 gluingArray = [{ type: "one-point", ip: instObj.headAnchor, t1: "head", tp: { x: 0, y: 0 } }]; 5024 } 5025 const availablePlugs = Array.from(/* @__PURE__ */ new Set([...getFamilyDefaultPlugs(instObj.family || "other"), ...Object.keys(instObj.plugs || {})])); 5026 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [ 5027 gluingArray.map((glue, idx) => { 5028 var _a3; 5029 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-black/40 border border-zinc-800 p-2 rounded relative", children: [
5030 /* @__PURE__ */ jsxRuntimeExports.jsx( 5031 "button", 5032 { 5033 type: "button", 5034 onClick: () => { 5035 const newGluing = [...gluingArray]; 5036 newGluing.splice(idx, 1); 5037 setInstrumentConfigs((prev) => { 5038 const typeObj = prev[selectedInstType]; 5039 const io = typeObj[selectedInstIndex] || {}; 5040 return { ...prev, [selectedInstType]: { ...typeObj, [selectedInstIndex]: { ...io, gluing: newGluing.length > 0 ? newGluing : void 0 } } }; 5041 }); 5042 }, 5043 className: "absolute top-1 right-1 text-red-500 hover:text-red-400 text-xs px-1", 5044 children: "â" 5045 } 5046 ), 5047 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-2 mt-2", children: [
5048 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 5049 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] text-zinc-400 uppercase", children: "Gluing Type" }), 5050 /* @__PURE__ */ jsxRuntimeExports.jsxs( 5051 "select", 5052 { 5053 value: glue.type || "none", 5054 onChange: (e) => { 5055 const val = e.target.value; 5056 const newGluing = [...gluingArray]; 5057 newGluing[idx] = { ...glue, type: val }; 5058 setInstrumentConfigs((prev) => { 5059 const typeObj = prev[selectedInstType]; 5060 const io = typeObj[selectedInstIndex] || {}; 5061 return { ...prev, [selectedInstType]: { ...typeObj, [selectedInstIndex]: { ...io, gluing: newGluing } } }; 5062 }); 5063 }, 5064 className: "w-full bg-black border border-zinc-700 rounded p-1 text-white text-xs", 5065 children: [
5066 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "none", children: "None / Disabled" }), 5067 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "one-point", children: "1-Point Glue" }) 5068 ] 5069 } 5070 ) 5071 ] }), 5072 glue.type === "one-point" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ 5073 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 5074 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] text-zinc-400 uppercase", children: "Instrument Plug" }), 5075 /* @__PURE__ */ jsxRuntimeExports.jsx( 5076 "select", 5077 { 5078 value: glue.plugName || ((_a3 = Object.entries(instObj.plugs || {}).find(([k, v]) => { 5079 var _a4, _b3; 5080 return (v == null ? void 0 : v.x) === ((_a4 = glue.ip) == null ? void 0 : _a4.x) && (v == null ? void 0 : v.y) === ((_b3 = glue.ip) == null ? void 0 : _b3.y); 5081 })) == null ? void 0 : _a3[0]) || "mouthpiece", 5082 onChange: (e) => { 5083 const val = e.target.value; 5084 const plugs = instObj.plugs || {}; 5085 let ip = { x: 0, y: 0 }; 5086 if (val === "mouthpiece") ip = plugs.mouthpiece || { x: 0, y: 0 }; 5087 else ip = plugs[val] || { x: 0, y: 0 }; 5088 const newGluing = [...gluingArray]; 5089 newGluing[idx] = { ...glue, plugName: val, ip }; 5090 setInstrumentConfigs((prev) => { 5091 const typeObj = prev[selectedInstType]; 5092 const io = typeObj[selectedInstIndex] || {}; 5093 return { ...prev, [selectedInstType]: { ...typeObj, [selectedInstIndex]: { ...io, gluing: newGluing } } }; 5094 }); 5095 }, 5096 className: "w-full bg-black border border-zinc-700 rounded p-1 text-white text-xs", 5097 children: availablePlugs.map((part) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: part, children: part }, part)) 5098 } 5099 ) 5100 ] }),
5101 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 5102 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] text-zinc-400 uppercase", children: "Target Body Part" }), 5103 /* @__PURE__ */ jsxRuntimeExports.jsx( 5104 "select", 5105 { 5106 value: glue.t1 || "head", 5107 onChange: (e) => { 5108 const val = e.target.value; 5109 const newGluing = [...gluingArray]; 5110 newGluing[idx] = { ...glue, t1: val }; 5111 setInstrumentConfigs((prev) => { 5112 const typeObj = prev[selectedInstType]; 5113 const io = typeObj[selectedInstIndex] || {}; 5114 return { ...prev, [selectedInstType]: { ...typeObj, [selectedInstIndex]: { ...io, gluing: newGluing } } }; 5115 }); 5116 }, 5117 className: "w-full bg-black border border-zinc-700 rounded p-1 text-white text-xs", 5118 children: ["head", "body", "shoulder_l", "elbow_l", "hand_l", "shoulder_r", "elbow_r", "hand_r", "leg_l", "foot_l", "leg_r", "foot_r"].map((part) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: part, children: part }, part)) 5119 } 5120 ) 5121 ] }), 5122 (glue.t1 === "hand_l" || glue.t1 === "hand_r") && /* @__P
5122URE__ */ jsxRuntimeExports.jsxs("div", { className: "col-span-2", children: [ 5123 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] text-zinc-400 uppercase", children: "Hand Grip Offset (from instrument anchor)" }), 5124 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex gap-2 mt-1", children: ["x", "y"].map((axis) => { 5125 var _a4; 5126 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [ 5127 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-zinc-500 uppercase", children: axis }), 5128 /* @__PURE__ */ jsxRuntimeExports.jsx( 5129 "input", 5130 { 5131 type: "number", 5132 value: ((_a4 = glue.tp) == null ? void 0 : _a4[axis]) ?? 0, 5133 step: 1, 5134 onChange: (e) => { 5135 const raw = e.target.value; 5136 if (raw === "" || raw === "-") return; 5137 const val = parseFloat(raw); 5138 if (isNaN(val)) return; 5139 const newGluing = [...gluingArray]; 5140 const otherAxis = axis === "x" ? "y" : "x"; 5141 newGluing[idx] = { ...glue, tp: { ...glue.tp || { [otherAxis]: 0 }, [axis]: val } }; 5142 setInstrumentConfigs((prev) => { 5143 const typeObj = prev[selectedInstType]; 5144 const io = typeObj[selectedInstIndex] || {}; 5145 return { ...prev, [selectedInstType]: { ...typeObj, [selectedInstIndex]: { ...io, gluing: newGluing } } }; 5146 }); 5147 }, 5148 onKeyDown: (e) => { 5149 var _a5; 5150 if (e.key === "ArrowUp" || e.key === "ArrowDown") { 5151 e.preventDefault(); 5152 const step = e.shiftKey ? 5 : 1; 5153 const dir = e.key === "ArrowUp" ? -1 : 1; 5154 const current = ((_a5 = glue.tp) == null ? void 0 : _a5[axis]) ?? 0; 5155 const val = current + dir * step; 5156 const newGluing = [...gluingArray]; 5157 const otherAxis = axis === "x" ? "y" : "x"; 5158 newGluing[idx] = { ...glue, tp: { ...glue.tp || { [otherAxis]: 0 }, [axis]: val } }; 5159 setInstrumentConfigs((prev) => { 5160 const typeObj = prev[selectedInstType]; 5161 const io = typeObj[selectedInstIndex] || {}; 5162 return { ...prev, [selectedInstType]: { ...typeObj, [selectedInstIndex]: { ...io, gluing: newGluing } } }; 5163 }); 5164 } 5165 }, 5166 className: "w-16 bg-black border border-zinc-700 rounded p-1 text-white text-xs" 5167 } 5168 ) 5169 ] }, axis); 5170 }) }),
5171 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[9px] text-zinc-600 mt-0.5", children: "Offset from instrument anchor. (0,0)=anchor point. Arrow keys ±1, Shift+Arrow ±5." }) 5172 ] }) 5173 ] }) 5174 ] }) 5175 ] }, idx); 5176 }), 5177 /* @__PURE__ */ jsxRuntimeExports.jsx( 5178 "button", 5179 { 5180 type: "button", 5181 onClick: () => { 5182 const newGluing = [...gluingArray, { type: "one-point", t1: "head", tp: { x: 0, y: 0 } }]; 5183 setInstrumentConfigs((prev) => { 5184 const typeObj = prev[selectedInstType]; 5185 const io = typeObj[selectedInstIndex] || {}; 5186 return { ...prev, [selectedInstType]: { ...typeObj, [selectedInstIndex]: { ...io, gluing: newGluing } } }; 5187 }); 5188 }, 5189 className: "text-xs bg-zinc-800 text-zinc-300 px-2 py-1 rounded hover:bg-zinc-700", 5190 children: "+ Add Gluing Point" 5191 } 5192 ) 5193 ] }); 5194 })() 5195 ] }),
5196 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-900 border border-zinc-700 p-3 rounded space-y-3", children: [ 5197 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs font-bold text-indigo-400 uppercase tracking-wider", children: "Instrument Plugs & Alignment Points" }), 5198 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-500", children: "Specify plug coordinates on the instrument (e.g. where hands grip or mouth locks). Select the target character socket it associates with. Click ð¯ to click-position on preview." }), 5199 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-2 text-zinc-300", children: (() => { 5200 const defaultPlugs = getFamilyDefaultPlugs((selectedInstSvg == null ? void 0 : selectedInstSvg.family) || "other"); 5201 const configPlugs = Object.keys((selectedInstSvg == null ? void 0 : selectedInstSvg.plugs) || {}); 5202 const allPlugs = Array.from(/* @__PURE__ */ new Set([...defaultPlugs, ...configPlugs])); 5203 const availableSockets = Array.from(/* @__PURE__ */ new Set([ 5204 "mouth", 5205 "shoulder_l", 5206 "shoulder_r", 5207 "elbow_l", 5208 "elbow_r", 5209 "hand_l", 5210 "hand_r", 5211 ...Object.keys((selectedConfig == null ? void 0 : selectedConfig.sockets) || {}) 5212 ])); 5213 const getAssociatedSocket = (name, plug) => { 5214 if (plug == null ? void 0 : plug.socket) return plug.socket; 5215 if (name === "mouthpiece") return "mouth"; 5216 if (name === "hand_l") return "hand_l"; 5217 if (name === "hand_r") return "hand_r"; 5218 return "none"; 5219 }; 5220 return allPlugs.map((plugName) => { 5221 var _a3; 5222 const svgVal = svgCoords[`plug_${plugName}`]; 5223 const configVal = (_a3 = selectedInstSvg.plugs) == null ? void 0 : _a3[plugName]; 5224 const isSvgOnly = svgVal && !configVal; 5225 const displayX = (configVal == null ? void 0 : configVal.x) ?? (svgVal == null ? void 0 : svgVal.x) ?? ""; 5226 const displayY = (configVal == null ? void 0 : configVal.y) ?? (svgVal == null ? void 0 : svgVal.y) ?? ""; 5227 const currentSocket = getAssociatedSocket(plugName, configVal); 5228 const isEditingThis = editMode === `plug_${plugName}`; 5229 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `bg-black/40 border rounded p-2 grid grid-cols-12 gap-2 items-center ${isEditingThis ? "border-emerald-500 bg-emerald-950/10" : "border-zinc-800"}`, children: [
5230 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "col-span-3 text-xs font-bold text-zinc-400 truncate flex flex-col gap-0.5", children: [ 5231 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", title: plugName, children: plugName }), 5232 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1", children: [ 5233 isSvgOnly && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[7px] bg-zinc-805 text-zinc-400 px-1 rounded", children: "SVG" }), 5234 !svgVal && !configVal && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[7px] bg-red-900/40 text-red-400 px-1 rounded", children: "Missing" }) 5235 ] }) 5236 ] }), 5237 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "col-span-2", children: [ 5238 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[8px] text-zinc-500 uppercase", children: "X Coord" }),
5239 /* @__PURE__ */ jsxRuntimeExports.jsx( 5240 "input", 5241 { 5242 type: "number", 5243 value: displayX, 5244 placeholder: svgVal ? `SVG: ${svgVal.x}` : "None", 5245 onChange: (e) => { 5246 const val = parseFloat(e.target.value); 5247 setInstrumentConfigs((prev) => { 5248 const typeObj = prev[selectedInstType]; 5249 const instObj = typeObj[selectedInstIndex] || {}; 5250 const currentPlugs = instObj.plugs || {}; 5251 const newX = isNaN(val) ? 0 : val; 5252 const newY = (configVal == null ? void 0 : configVal.y) ?? (svgVal == null ? void 0 : svgVal.y) ?? 0; 5253 const updated = { 5254 ...instObj, 5255 plugs: { 5256 ...currentPlugs, 5257 [plugName]: { ...currentPlugs[plugName] || {}, x: newX, y: newY } 5258 } 5259 }; 5260 return { 5261 ...prev, 5262 [selectedInstType]: { 5263 ...typeObj, 5264 [selectedInstIndex]: updated 5265 } 5266 }; 5267 }); 5268 }, 5269 className: "w-full bg-black border border-zinc-700 rounded p-1 text-white text-[10px]" 5270 } 5271 ) 5272 ] }),
5273 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "col-span-2", children: [ 5274 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[8px] text-zinc-500 uppercase", children: "Y Coord" }), 5275 /* @__PURE__ */ jsxRuntimeExports.jsx( 5276 "input", 5277 { 5278 type: "number", 5279 value: displayY, 5280 placeholder: svgVal ? `SVG: ${svgVal.y}` : "None", 5281 onChange: (e) => { 5282 const val = parseFloat(e.target.value); 5283 setInstrumentConfigs((prev) => { 5284 const typeObj = prev[selectedInstType]; 5285 const instObj = typeObj[selectedInstIndex] || {}; 5286 const currentPlugs = instObj.plugs || {}; 5287 const newX = (configVal == null ? void 0 : configVal.x) ?? (svgVal == null ? void 0 : svgVal.x) ?? 0; 5288 const newY = isNaN(val) ? 0 : val; 5289 const updated = { 5290 ...instObj, 5291 plugs: { 5292 ...currentPlugs, 5293 [plugName]: { ...currentPlugs[plugName] || {}, x: newX, y: newY } 5294 } 5295 }; 5296 return { 5297 ...prev, 5298 [selectedInstType]: { 5299 ...typeObj, 5300 [selectedInstIndex]: updated 5301 } 5302 }; 5303 }); 5304 }, 5305 className: "w-full bg-black border border-zinc-700 rounded p-1 text-white text-[10px]" 5306 } 5307 ) 5308 ] }),
5309 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "col-span-3", children: [ 5310 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[8px] text-zinc-500 uppercase", children: "Socket" }), 5311 /* @__PURE__ */ jsxRuntimeExports.jsxs( 5312 "select", 5313 { 5314 value: currentSocket, 5315 onChange: (e) => { 5316 const socketVal = e.target.value; 5317 pushUndo(); 5318 setInstrumentConfigs((prev) => { 5319 const typeObj = prev[selectedInstType]; 5320 const instObj = typeObj[selectedInstIndex] || {}; 5321 const currentPlugs = instObj.plugs || {}; 5322 const currentCoords = configVal || svgVal || { x: 0, y: 0 }; 5323 return { 5324 ...prev, 5325 [selectedInstType]: { 5326 ...typeObj, 5327 [selectedInstIndex]: { 5328 ...instObj, 5329 plugs: { 5330 ...currentPlugs, 5331 [plugName]: { 5332 ...currentCoords, 5333 socket: socketVal 5334 } 5335 } 5336 } 5337 } 5338 }; 5339 }); 5340 }, 5341 className: "w-full bg-black border border-zinc-700 rounded p-1 text-white text-[10px]", 5342 children: [
5343 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "none", children: "None" }), 5344 availableSockets.map((s) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: s, children: s }, s)) 5345 ] 5346 } 5347 ) 5348 ] }), 5349 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "col-span-2 flex justify-end gap-1", children: [ 5350 /* @__PURE__ */ jsxRuntimeExports.jsx( 5351 "button", 5352 { 5353 type: "button", 5354 onClick: () => setEditMode(isEditingThis ? "none" : `plug_${plugName}`), 5355 className: `p-1 rounded border transition-colors ${isEditingThis ? "bg-emerald-600 border-emerald-500 text-white" : "bg-zinc-800 border-zinc-700 text-zinc-400 hover:bg-zinc-750 hover:text-white"}`, 5356 title: "Pin on Canvas", 5357 children: "ð¯" 5358 } 5359 ), 5360 !isSvgOnly && /* @__PURE__ */ jsxRuntimeExports.jsx( 5361 "button", 5362 { 5363 type: "button", 5364 onClick: () => { 5365 pushUndo(); 5366 setInstrumentConfigs((prev) => { 5367 const typeObj = prev[selectedInstType]; 5368 const instObj = typeObj[selectedInstIndex] || {}; 5369 const currentPlugs = instObj.plugs ? { ...instObj.plugs } : { 5370 mouthpiece: { x: 0, y: -50, socket: "mouth" }, 5371 hand_l: { x: 0, y: 0, socket: "hand_l" }, 5372 hand_r: { x: 0, y: 0, socket: "hand_r" } 5373 }; 5374 delete currentPlugs[plugName]; 5375 return { 5376 ...prev, 5377 [selectedInstType]: { 5378 ...typeObj, 5379 [selectedInstIndex]: { 5380 ...instObj, 5381 plugs: currentPlugs 5382 } 5383 } 5384 }; 5385 }); 5386 }, 5387 className: "p-1 rounded border bg-red-950/20 border-red-900/40 text-red-400 hover:bg-red-900/30 transition-colors", 5388 title: "Remove Plug", 5389 children: "â" 5390 } 5391 ) 5392 ] }) 5393 ] }, plugName); 5394 }); 5395 })() }),
5396 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1.5 border-t border-zinc-800/60 pt-2.5", children: [ 5397 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[9px] text-zinc-500 uppercase font-bold", children: "Add Custom Plug / Alignment Point" }), 5398 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [ 5399 /* @__PURE__ */ jsxRuntimeExports.jsx( 5400 "input", 5401 { 5402 type: "text", 5403 id: "new-plug-name-input", 5404 placeholder: "Name (e.g. strap)", 5405 className: "flex-1 bg-black border border-zinc-700 rounded p-1 text-white text-[10px]" 5406 } 5407 ), 5408 (() => { 5409 const availableSockets = Array.from(/* @__PURE__ */ new Set([ 5410 "mouth", 5411 "shoulder_l", 5412 "shoulder_r", 5413 "elbow_l", 5414 "elbow_r", 5415 "hand_l", 5416 "hand_r", 5417 ...Object.keys((selectedConfig == null ? void 0 : selectedConfig.sockets) || {}) 5418 ])); 5419 return /* @__PURE__ */ jsxRuntimeExports.jsxs( 5420 "select", 5421 { 5422 id: "new-plug-socket-select", 5423 className: "w-24 bg-black border border-zinc-700 rounded p-1 text-white text-[10px]", 5424 children: [
5425 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "none", children: "No Socket" }), 5426 availableSockets.map((s) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: s, children: s }, s)) 5427 ] 5428 } 5429 ); 5430 })(), 5431 /* @__PURE__ */ jsxRuntimeExports.jsx( 5432 "button", 5433 { 5434 type: "button", 5435 onClick: () => { 5436 var _a3, _b3; 5437 const input = document.getElementById("new-plug-name-input"); 5438 const socketSelect = document.getElementById("new-plug-socket-select"); 5439 const name = (_b3 = (_a3 = input == null ? void 0 : input.value) == null ? void 0 : _a3.trim()) == null ? void 0 : _b3.toLowerCase(); 5440 const socketVal = (socketSelect == null ? void 0 : socketSelect.value) || "none"; 5441 if (name && selectedInstSvg) { 5442 pushUndo(); 5443 setInstrumentConfigs((prev) => { 5444 const typeObj = prev[selectedInstType]; 5445 const instObj = typeObj[selectedInstIndex] || {}; 5446 const currentPlugs = instObj.plugs || {}; 5447 return { 5448 ...prev, 5449 [selectedInstType]: { 5450 ...typeObj, 5451 [selectedInstIndex]: { 5452 ...instObj, 5453 plugs: { 5454 ...currentPlugs, 5455 [name]: currentPlugs[name] || { x: 0, y: 0, socket: socketVal } 5456 } 5457 } 5458 } 5459 }; 5460 }); 5461 input.value = ""; 5462 socketSelect.value = "none"; 5463 } 5464 }, 5465 className: "bg-indigo-600 hover:bg-indigo-500 text-white rounded px-2 text-[10px] font-bold", 5466 children: "Add" 5467 } 5468 ) 5469 ] }) 5470 ] }) 5471 ] }),
5472 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-900 border border-zinc-700 p-3 rounded space-y-4", children: [ 5473 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs font-bold text-indigo-400 uppercase tracking-wider", children: "Motion Physics & Rest Pose" }), 5474 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [ 5475 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] text-zinc-400 uppercase font-bold", children: "Rest Pose (Fallback positions for hands)" }), 5476 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-4 bg-black/40 p-2 border border-zinc-800 rounded", children: [ 5477 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 5478 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "block text-[9px] text-zinc-500 uppercase mb-1", children: "Left Hand (X, Y)" }), 5479 /* @__P
5479URE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [ 5480 /* @__PURE__ */ jsxRuntimeExports.jsx( 5481 "input", 5482 { 5483 type: "number", 5484 placeholder: "X (e.g. -30)", 5485 value: ((_b2 = (_a2 = selectedInstSvg.restPose) == null ? void 0 : _a2.leftHand) == null ? void 0 : _b2.x) ?? "", 5486 onChange: (e) => { 5487 const val = parseFloat(e.target.value); 5488 setInstrumentConfigs((prev) => { 5489 const typeObj = prev[selectedInstType]; 5490 const instObj = typeObj[selectedInstIndex] || {}; 5491 const rp = instObj.restPose || {}; 5492 const lh = rp.leftHand || {}; 5493 return { 5494 ...prev, 5495 [selectedInstType]: { 5496 ...typeObj, 5497 [selectedInstIndex]: { 5498 ...instObj, 5499 restPose: { ...rp, leftHand: isNaN(val) ? void 0 : { ...lh, x: val } } 5500 } 5501 } 5502 }; 5503 }); 5504 }, 5505 className: "w-1/2 bg-black border border-zinc-700 rounded p-1 text-white text-xs" 5506 } 5507 ),
5508 /* @__PURE__ */ jsxRuntimeExports.jsx( 5509 "input", 5510 { 5511 type: "number", 5512 placeholder: "Y (e.g. 10)", 5513 value: ((_d2 = (_c2 = selectedInstSvg.restPose) == null ? void 0 : _c2.leftHand) == null ? void 0 : _d2.y) ?? "", 5514 onChange: (e) => { 5515 const val = parseFloat(e.target.value); 5516 setInstrumentConfigs((prev) => { 5517 const typeObj = prev[selectedInstType]; 5518 const instObj = typeObj[selectedInstIndex] || {}; 5519 const rp = instObj.restPose || {}; 5520 const lh = rp.leftHand || {}; 5521 return { 5522 ...prev, 5523 [selectedInstType]: { 5524 ...typeObj, 5525 [selectedInstIndex]: { 5526 ...instObj, 5527 restPose: { ...rp, leftHand: isNaN(val) ? void 0 : { ...lh, y: val } } 5528 } 5529 } 5530 }; 5531 }); 5532 }, 5533 className: "w-1/2 bg-black border border-zinc-700 rounded p-1 text-white text-xs" 5534 } 5535 ) 5536 ] }) 5537 ] }),
5538 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 5539 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "block text-[9px] text-zinc-500 uppercase mb-1", children: "Right Hand (X, Y)" }), 5540 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [ 5541 /* @__PURE__ */ jsxRuntimeExports.jsx( 5542 "input", 5543 { 5544 type: "number", 5545 placeholder: "X (e.g. 30)", 5546 value: ((_f2 = (_e2 = selectedInstSvg.restPose) == null ? void 0 : _e2.rightHand) == null ? void 0 : _f2.x) ?? "", 5547 onChange: (e) => { 5548 const val = parseFloat(e.target.value); 5549 setInstrumentConfigs((prev) => { 5550 const typeObj = prev[selectedInstType]; 5551 const instObj = typeObj[selectedInstIndex] || {}; 5552 const rp = instObj.restPose || {}; 5553 const rh = rp.rightHand || {}; 5554 return { 5555 ...prev, 5556 [selectedInstType]: { 5557 ...typeObj, 5558 [selectedInstIndex]: { 5559 ...instObj, 5560 restPose: { ...rp, rightHand: isNaN(val) ? void 0 : { ...rh, x: val } } 5561 } 5562 } 5563 }; 5564 }); 5565 }, 5566 className: "w-1/2 bg-black border border-zinc-700 rounded p-1 text-white text-xs" 5567 } 5568 ),
5569 /* @__PURE__ */ jsxRuntimeExports.jsx( 5570 "input", 5571 { 5572 type: "number", 5573 placeholder: "Y (e.g. 10)", 5574 value: ((_h2 = (_g2 = selectedInstSvg.restPose) == null ? void 0 : _g2.rightHand) == null ? void 0 : _h2.y) ?? "", 5575 onChange: (e) => { 5576 const val = parseFloat(e.target.value); 5577 setInstrumentConfigs((prev) => { 5578 const typeObj = prev[selectedInstType]; 5579 const instObj = typeObj[selectedInstIndex] || {}; 5580 const rp = instObj.restPose || {}; 5581 const rh = rp.rightHand || {}; 5582 return { 5583 ...prev, 5584 [selectedInstType]: { 5585 ...typeObj, 5586 [selectedInstIndex]: { 5587 ...instObj, 5588 restPose: { ...rp, rightHand: isNaN(val) ? void 0 : { ...rh, y: val } } 5589 } 5590 } 5591 }; 5592 }); 5593 }, 5594 className: "w-1/2 bg-black border border-zinc-700 rounded p-1 text-white text-xs" 5595 } 5596 ) 5597 ] }) 5598 ] }) 5599 ] }) 5600 ] }),
5601 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [ 5602 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] text-zinc-400 uppercase font-bold", children: "Note Range & Physical Spread" }), 5603 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-3 gap-2 bg-black/40 p-2 border border-zinc-800 rounded", children: [ 5604 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 5605 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "block text-[9px] text-zinc-500 uppercase mb-1", children: "Min Note (0-127)" }), 5606 /* @__PURE__ */ jsxRuntimeExports.jsx( 5607 "input", 5608 { 5609 type: "number", 5610 placeholder: "Min", 5611 value: ((_i2 = selectedInstSvg.noteRange) == null ? void 0 : _i2.minNote) ?? "", 5612 onChange: (e) => { 5613 const val = parseInt(e.target.value); 5614 setInstrumentConfigs((prev) => { 5615 const typeObj = prev[selectedInstType]; 5616 const instObj = typeObj[selectedInstIndex] || {}; 5617 const nr = instObj.noteRange || {}; 5618 return { 5619 ...prev, 5620 [selectedInstType]: { 5621 ...typeObj, 5622 [selectedInstIndex]: { 5623 ...instObj, 5624 noteRange: { ...nr, minNote: isNaN(val) ? void 0 : val } 5625 } 5626 } 5627 }; 5628 }); 5629 }, 5630 className: "w-full bg-black border border-zinc-700 rounded p-1 text-white text-[10px]" 5631 } 5632 ) 5633 ] }),
5634 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 5635 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "block text-[9px] text-zinc-500 uppercase mb-1", children: "Max Note (0-127)" }), 5636 /* @__PURE__ */ jsxRuntimeExports.jsx( 5637 "input", 5638 { 5639 type: "number", 5640 placeholder: "Max", 5641 value: ((_j2 = selectedInstSvg.noteRange) == null ? void 0 : _j2.maxNote) ?? "", 5642 onChange: (e) => { 5643 const val = parseInt(e.target.value); 5644 setInstrumentConfigs((prev) => { 5645 const typeObj = prev[selectedInstType]; 5646 const instObj = typeObj[selectedInstIndex] || {}; 5647 const nr = instObj.noteRange || {}; 5648 return { 5649 ...prev, 5650 [selectedInstType]: { 5651 ...typeObj, 5652 [selectedInstIndex]: { 5653 ...instObj, 5654 noteRange: { ...nr, maxNote: isNaN(val) ? void 0 : val } 5655 } 5656 } 5657 }; 5658 }); 5659 }, 5660 className: "w-full bg-black border border-zinc-700 rounded p-1 text-white text-[10px]" 5661 } 5662 ) 5663 ] }),
5664 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 5665 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "block text-[9px] text-zinc-500 uppercase mb-1", children: "Physical Width (px)" }), 5666 /* @__PURE__ */ jsxRuntimeExports.jsx( 5667 "input", 5668 { 5669 type: "number", 5670 placeholder: "Width", 5671 value: ((_k2 = selectedInstSvg.noteRange) == null ? void 0 : _k2.physicalLength) ?? "", 5672 onChange: (e) => { 5673 const val = parseInt(e.target.value); 5674 setInstrumentConfigs((prev) => { 5675 const typeObj = prev[selectedInstType]; 5676 const instObj = typeObj[selectedInstIndex] || {}; 5677 const nr = instObj.noteRange || {}; 5678 return { 5679 ...prev, 5680 [selectedInstType]: { 5681 ...typeObj, 5682 [selectedInstIndex]: { 5683 ...instObj, 5684 noteRange: { ...nr, physicalLength: isNaN(val) ? void 0 : val } 5685 } 5686 } 5687 }; 5688 }); 5689 }, 5690 className: "w-full bg-black border border-zinc-700 rounded p-1 text-white text-[10px]" 5691 } 5692 ) 5693 ] }) 5694 ] }) 5695 ] }),
5696 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [ 5697 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] text-zinc-400 uppercase font-bold", children: "IK Smooth & Motion Physics" }), 5698 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "bg-black/40 p-2 border border-zinc-800 rounded space-y-2", children: [ 5699 { key: "swayFrequency", label: "Sway Frequency", defaultVal: 5e-3, min: 1e-3, max: 0.05, step: 1e-3 }, 5700 { key: "strikeFrequency", label: "Strike Frequency", defaultVal: 0.02, min: 5e-3, max: 0.1, step: 5e-3 }, 5701 { key: "strikeAmplitude", label: "Strike Amp (px)", defaultVal: 10, min: 0, max: 40, step: 1 }, 5702 { key: "bowPhaseStep", label: "Bow Phase Step", defaultVal: 0.05, min: 0.01, max: 0.2, step: 0.01 }, 5703 { key: "bowGapMs", label: "Bow Gap (ms)", defaultVal: 500, min: 50, max: 2e3, step: 50 }, 5704 { key: "strumSpeedMult", label: "Strum Speed Mult", defaultVal: 0.03, min: 5e-3, max: 0.15, step: 5e-3 }, 5705 { key: "strumVerticalAmp", label: "Strum Vert Amp", defaultVal: 15, min: 0, max: 60, step: 1 }, 5706 { key: "strumHorizontalAmp", label: "Strum Horiz Amp", defaultVal: 5, min: 0, max: 30, step: 1 }, 5707 { key: "ikLerpL", label: "IK Lerp Left", defaultVal: 0.3, min: 0.01, max: 1, step: 0.01 }, 5708 { key: "ikLerpR", label: "IK Lerp Right", defaultVal: 0.3, min: 0.01, max: 1, step: 0.01 } 5709 ].map(({ key, label, defaultVal, min, max, step }) => { 5710 var _a3; 5711 const currentVal = ((_a3 = selectedInstSvg.motionPhysics) == null ? void 0 : _a3[key]) ?? defaultVal; 5712 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
5713 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-zinc-400 text-xs w-28 shrink-0", children: label }), 5714 /* @__PURE__ */ jsxRuntimeExports.jsx( 5715 "input", 5716 { 5717 type: "range", 5718 min, 5719 max, 5720 step, 5721 value: currentVal, 5722 onChange: (e) => { 5723 const val = parseFloat(e.target.value); 5724 setInstrumentConfigs((prev) => { 5725 const typeObj = prev[selectedInstType]; 5726 const instObj = typeObj[selectedInstIndex] || {}; 5727 const mp = instObj.motionPhysics || {}; 5728 return { 5729 ...prev, 5730 [selectedInstType]: { 5731 ...typeObj, 5732 [selectedInstIndex]: { 5733 ...instObj, 5734 motionPhysics: { ...mp, [key]: val } 5735 } 5736 } 5737 }; 5738 }); 5739 }, 5740 className: "flex-1 accent-indigo-500 h-1" 5741 } 5742 ),
5743 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-zinc-300 text-xs w-10 text-right", children: currentVal }), 5744 /* @__PURE__ */ jsxRuntimeExports.jsx( 5745 "button", 5746 { 5747 onClick: () => { 5748 setInstrumentConfigs((prev) => { 5749 const typeObj = prev[selectedInstType]; 5750 const instObj = typeObj[selectedInstIndex] || {}; 5751 const mp = { ...instObj.motionPhysics || {} }; 5752 delete mp[key]; 5753 return { 5754 ...prev, 5755 [selectedInstType]: { 5756 ...typeObj, 5757 [selectedInstIndex]: { 5758 ...instObj, 5759 motionPhysics: Object.keys(mp).length > 0 ? mp : void 0 5760 } 5761 } 5762 }; 5763 }); 5764 }, 5765 title: "Reset override", 5766 className: "text-zinc-500 hover:text-zinc-300 text-xs", 5767 children: "âº" 5768 } 5769 ) 5770 ] }, key); 5771 }) }) 5772 ] }) 5773 ] }) 5774 ] }),
5775 /* @__PURE__ */ jsxRuntimeExports.jsx( 5776 "textarea", 5777 { 5778 value: selectedInstSvg ? typeof selectedInstSvg === "string" ? selectedInstSvg : JSON.stringify(selectedInstSvg, null, 2) : "", 5779 onChange: (e) => { 5780 try { 5781 const parsed = JSON.parse(e.target.value); 5782 setInstrumentConfigs((prev) => ({ 5783 ...prev, 5784 [selectedInstType]: { ...prev[selectedInstType], [selectedInstIndex]: parsed } 5785 })); 5786 } catch { 5787 } 5788 }, 5789 className: "w-full h-64 bg-black border border-zinc-700 rounded p-3 text-emerald-400 font-mono text-sm resize-y" 5790 } 5791 ) 5792 ] }); 5793 })(), 5794 activeTab === "clothing" && selectedClothingDef && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "max-w-4xl space-y-8", children: [ 5795 (() => { 5796 const warnings = []; 5797 Object.keys(selectedClothingDef.parts).forEach((part) => { 5798 var _a2, _b2, _c2, _d2, _e2; 5799 const glue = (_a2 = selectedClothingDef.gluing) == null ? void 0 : _a2[part]; 5800 if (!glue || glue.type === "none") return; 5801 const svgContent = selectedClothingDef.parts[part] || ""; 5802 const svgCoords = parseSvgSocketsAndPlugs(svgContent); 5803 const hasP1 = svgCoords["plug_p1"] !== void 0 || ((_c2 = (_b2 = selectedClothingDef.plugs) == null ? void 0 : _b2[part]) == null ? void 0 : _c2.p1) !== void 0 || glue.p1 !== void 0; 5804 const hasP2 = svgCoords["plug_p2"] !== void 0 || ((_e2 = (_d2 = selectedClothingDef.plugs) == null ? void 0 : _d2[part]) == null ? void 0 : _e2.p2) !== void 0 || glue.p2 !== void 0; 5805 const missing = []; 5806 if (!hasP1) missing.push("p1"); 5807 if (glue.type === "two-point" && !hasP2) missing.push("p2"); 5808 if (missing.length > 0) { 5809 warnings.push({ part, missing }); 5810 } 5811 }); 5812 if (warnings.length === 0) return null; 5813 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-amber-950/40 border border-amber-800 text-amber-200 p-3 rounded-lg flex flex-col gap-1 text-xs", children: [
5814 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "font-bold flex items-center gap-1.5 text-amber-400", children: [ 5815 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm", children: "â ï¸" }), 5816 " Missing Clothing Gluing Plugs" 5817 ] }), 5818 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-zinc-400 font-normal", children: "The following clothing parts have gluing enabled but are missing plugs in their SVG/config overrides:" }), 5819 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 space-y-1", children: warnings.map((w) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 5820 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-indigo-400 font-semibold uppercase font-mono", children: w.part }), 5821 ":", 5822 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex gap-1", children: w.missing.map((m) => /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "px-1.5 py-0.5 bg-amber-900/40 border border-amber-700/50 rounded text-[10px] text-amber-300 font-mono font-semibold", children: [ 5823 "plug_", 5824 m 5825 ] }, m)) }) 5826 ] }, w.part)) }) 5827 ] }); 5828 })(),
5829 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-zinc-800 rounded-lg p-4 bg-zinc-900/50", children: [ 5830 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-between mb-3", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-xs font-bold text-zinc-500 uppercase tracking-wider", children: [ 5831 /* @__PURE__ */ jsxRuntimeExports.jsx(PenLine, { size: 16 }), 5832 " Clothing Config" 5833 ] }) }), 5834 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-4", children: [ 5835 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-500 uppercase tracking-wider mb-2", children: "ID (Name)" }), 5836 /* @__PURE__ */ jsxRuntimeExports.jsx( 5837 "input", 5838 { 5839 type: "text", 5840 value: selectedClothingDef.id, 5841 onChange: (e) => { 5842 const newId = e.target.value; 5843 const oldId = selectedClothingDef.id; 5844 if (newId === oldId) return; 5845 setClothingConfigs((prev) => { 5846 const newConfigs = { ...prev }; 5847 newConfigs[newId] = { ...newConfigs[oldId], id: newId }; 5848 delete newConfigs[oldId]; 5849 return newConfigs; 5850 }); 5851 setSelectedClothingId(newId); 5852 }, 5853 className: "w-full bg-black border border-zinc-700 rounded p-2 text-white" 5854 } 5855 ) 5856 ] }),
5857 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4 mt-8", children: [ 5858 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-sm font-bold text-white border-b border-zinc-800 pb-2", children: "Clothing Parts (SVGs)" }), 5859 ["head", "body", "upper_arm_l", "lower_arm_l", "hand_l", "upper_arm_r", "lower_arm_r", "hand_r", "leg_l", "foot_l", "leg_r", "foot_r"].map((part) => { 5860 var _a2, _b2, _c2, _d2, _e2, _f2, _g2, _h2, _i2, _j2, _k2, _l2, _m2, _n2, _o2, _p2, _q2, _r2, _s2, _t2, _u2, _v2, _w2, _x2, _y2, _z2, _A2, _B2, _C2, _D2, _E2, _F2, _G2, _H2; 5861 const isExpanded = !!expandedClothingParts[part]; 5862 const classSuffix = getDisplayClassName(part); 5863 const isEmpty = !selectedClothingDef || !selectedClothingDef.parts[part]; 5864 const isSelected = selectedPartId === part; 5865 return /* @__PURE__ */ jsxRuntimeExports.jsxs( 5866 "div", 5867 { 5868 onClick: () => setSelectedPartId(part), 5869 className: `transition-all duration-200 border rounded p-3 flex flex-col gap-2 cursor-pointer ${isSelected ? "bg-indigo-950/20 border-indigo-500 ring-1 ring-indigo-500/50 shadow-[0_0_8px_rgba(99,102,241,0.2)]" : "bg-black border border-zinc-800 hover:border-zinc-700"}`, 5870 children: [
5871 /* @__PURE__ */ jsxRuntimeExports.jsxs( 5872 "div", 5873 { 5874 className: "flex items-center justify-between cursor-pointer select-none", 5875 onClick: (e) => { 5876 setSelectedPartId(part); 5877 setExpandedClothingParts((prev) => ({ ...prev, [part]: !prev[part] })); 5878 }, 5879 children: [ 5880 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 5881 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-500 text-[10px] w-3 inline-block", children: isExpanded ? "â¼" : "â¶" }), 5882 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: `block text-xs uppercase tracking-wider cursor-pointer ${isEmpty ? "text-zinc-500 opacity-60" : "font-bold text-indigo-400"}`, children: [ 5883 part, 5884 classSuffix 5885 ] }) 5886 ] }),
5887 /* @__PURE__ */ jsxRuntimeExports.jsxs( 5888 "label", 5889 { 5890 onClick: (e) => e.stopPropagation(), 5891 className: "cursor-pointer flex items-center gap-1 text-[10px] px-2 py-0.5 bg-zinc-850 hover:bg-zinc-800 border border-zinc-700 text-zinc-300 rounded transition-colors", 5892 children: [ 5893 /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { size: 10 }), 5894 " Upload SVG", 5895 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "file", accept: ".svg", className: "hidden", onChange: (e) => { 5896 var _a3; 5897 pushUndo(); 5898 const file = (_a3 = e.target.files) == null ? void 0 : _a3[0]; 5899 if (!file) return; 5900 const reader = new FileReader(); 5901 reader.onload = (event) => { 5902 var _a4; 5903 try { 5904 const content = (_a4 = event.target) == null ? void 0 : _a4.result; 5905 if (!content.includes("<svg") || !content.includes("</svg>")) { 5906 alert("Invalid SVG file format"); 5907 return; 5908 } 5909 const match = content.match(/<svg[^>]*>([\s\S]*?)<\/svg>/i); 5910 const svgInner = match ? match[1] : content; 5911 setClothingConfigs((prev) => ({ 5912 ...prev, 5913 [selectedClothingDef.id]: { 5914 ...selectedClothingDef, 5915 parts: { 5916 ...selectedClothingDef.parts, 5917 [part]: svgInner 5918 } 5919 } 5920 })); 5921 } catch (err) { 5922 alert("Failed to parse SVG file"); 5923 } 5924 }; 5925 reader.readAsText(file); 5926 } }) 5927 ] 5928 } 5929 ) 5930 ] 5931 } 5932 ),
5933 isExpanded && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ 5934 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { onClick: (e) => e.stopPropagation(), className: "bg-zinc-900 border border-zinc-800 p-2.5 rounded flex flex-col gap-2 animate-fadeIn cursor-default", children: [ 5935 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between border-b border-zinc-800 pb-1.5 mb-1.5", children: [ 5936 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-bold text-zinc-400 uppercase", children: "Positioning & Alignment" }), 5937 /* @__PURE__ */ jsxRuntimeExports.jsxs( 5938 "select", 5939 { 5940 value: ((_b2 = (_a2 = selectedClothingDef.gluing) == null ? void 0 : _a2[part]) == null ? void 0 : _b2.type) || "none", 5941 onChange: (e) => { 5942 var _a3; 5943 pushUndo(); 5944 const val = e.target.value; 5945 const currentGluing = ((_a3 = selectedClothingDef.gluing) == null ? void 0 : _a3[part]) || { type: "none", p1: { x: 0, y: 0 }, p2: { x: 0, y: 40 }, t1: "shoulder_l", t2: "elbow_l" }; 5946 setClothingConfigs((prev) => ({ 5947 ...prev, 5948 [selectedClothingDef.id]: { 5949 ...selectedClothingDef, 5950 gluing: { 5951 ...selectedClothingDef.gluing, 5952 [part]: { ...currentGluing, type: val } 5953 } 5954 } 5955 })); 5956 }, 5957 className: "bg-black border border-zinc-700 rounded px-1.5 py-0.5 text-[9px] text-zinc-300", 5958 children: [
5959 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "none", children: "Standard Offset" }), 5960 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "one-point", children: "1-Point Glue" }), 5961 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "two-point", children: "2-Point Glue (Stretch)" }) 5962 ] 5963 } 5964 ) 5965 ] }), 5966 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-5 gap-2", children: [ 5967 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 5968 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] text-zinc-500 uppercase", children: "Scale X" }), 5969 /* @__PURE__ */ jsxRuntimeExports.jsx( 5970 "input", 5971 { 5972 type: "number", 5973 step: "0.1", 5974 value: ((_d2 = (_c2 = selectedClothingDef.transforms) == null ? void 0 : _c2[part]) == null ? void 0 : _d2.scaleX) ?? 1, 5975 onChange: (e) => { 5976 var _a3; 5977 pushUndo(); 5978 const val = parseFloat(e.target.value); 5979 const currentTransform = ((_a3 = selectedClothingDef.transforms) == null ? void 0 : _a3[part]) || { x: 0, y: 0, scaleX: 1, scaleY: 1, rotation: 0 }; 5980 setClothingConfigs((prev) => ({ ...prev, [selectedClothingDef.id]: { ...selectedClothingDef, transforms: { ...selectedClothingDef.transforms, [part]: { ...currentTransform, scaleX: val } } } })); 5981 }, 5982 className: "w-full bg-black border border-zinc-700 rounded p-1 text-white text-[10px]" 5983 } 5984 ) 5985 ] }),
5986 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 5987 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] text-zinc-500 uppercase", children: "Scale Y" }), 5988 /* @__PURE__ */ jsxRuntimeExports.jsx( 5989 "input", 5990 { 5991 type: "number", 5992 step: "0.1", 5993 value: ((_f2 = (_e2 = selectedClothingDef.transforms) == null ? void 0 : _e2[part]) == null ? void 0 : _f2.scaleY) ?? 1, 5994 onChange: (e) => { 5995 var _a3; 5996 pushUndo(); 5997 const val = parseFloat(e.target.value); 5998 const currentTransform = ((_a3 = selectedClothingDef.transforms) == null ? void 0 : _a3[part]) || { x: 0, y: 0, scaleX: 1, scaleY: 1, rotation: 0 }; 5999 setClothingConfigs((prev) => ({ ...prev, [selectedClothingDef.id]: { ...selectedClothingDef, transforms: { ...selectedClothingDef.transforms, [part]: { ...currentTransform, scaleY: val } } } })); 6000 }, 6001 className: "w-full bg-black border border-zinc-700 rounded p-1 text-white text-[10px]" 6002 } 6003 ) 6004 ] }),
6005 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6006 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] text-zinc-500 uppercase", children: "Offset X" }), 6007 /* @__PURE__ */ jsxRuntimeExports.jsx( 6008 "input", 6009 { 6010 type: "number", 6011 value: ((_h2 = (_g2 = selectedClothingDef.transforms) == null ? void 0 : _g2[part]) == null ? void 0 : _h2.x) ?? 0, 6012 onChange: (e) => { 6013 var _a3; 6014 pushUndo(); 6015 const val = parseFloat(e.target.value) || 0; 6016 const currentTransform = ((_a3 = selectedClothingDef.transforms) == null ? void 0 : _a3[part]) || { x: 0, y: 0, scaleX: 1, scaleY: 1, rotation: 0 }; 6017 setClothingConfigs((prev) => ({ ...prev, [selectedClothingDef.id]: { ...selectedClothingDef, transforms: { ...selectedClothingDef.transforms, [part]: { ...currentTransform, x: val } } } })); 6018 }, 6019 className: "w-full bg-black border border-zinc-700 rounded p-1 text-white text-[10px]" 6020 } 6021 ) 6022 ] }),
6023 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6024 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] text-zinc-500 uppercase", children: "Offset Y" }), 6025 /* @__PURE__ */ jsxRuntimeExports.jsx( 6026 "input", 6027 { 6028 type: "number", 6029 value: ((_j2 = (_i2 = selectedClothingDef.transforms) == null ? void 0 : _i2[part]) == null ? void 0 : _j2.y) ?? 0, 6030 onChange: (e) => { 6031 var _a3; 6032 pushUndo(); 6033 const val = parseFloat(e.target.value) || 0; 6034 const currentTransform = ((_a3 = selectedClothingDef.transforms) == null ? void 0 : _a3[part]) || { x: 0, y: 0, scaleX: 1, scaleY: 1, rotation: 0 }; 6035 setClothingConfigs((prev) => ({ ...prev, [selectedClothingDef.id]: { ...selectedClothingDef, transforms: { ...selectedClothingDef.transforms, [part]: { ...currentTransform, y: val } } } })); 6036 }, 6037 className: "w-full bg-black border border-zinc-700 rounded p-1 text-white text-[10px]" 6038 } 6039 ) 6040 ] }),
6041 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6042 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] text-zinc-500 uppercase", children: "Rotation (deg)" }), 6043 /* @__PURE__ */ jsxRuntimeExports.jsx( 6044 "input", 6045 { 6046 type: "number", 6047 value: ((_l2 = (_k2 = selectedClothingDef.transforms) == null ? void 0 : _k2[part]) == null ? void 0 : _l2.rotation) ?? 0, 6048 onChange: (e) => { 6049 var _a3; 6050 pushUndo(); 6051 const val = parseFloat(e.target.value) || 0; 6052 const currentTransform = ((_a3 = selectedClothingDef.transforms) == null ? void 0 : _a3[part]) || { x: 0, y: 0, scaleX: 1, scaleY: 1, rotation: 0 }; 6053 setClothingConfigs((prev) => ({ ...prev, [selectedClothingDef.id]: { ...selectedClothingDef, transforms: { ...selectedClothingDef.transforms, [part]: { ...currentTransform, rotation: val } } } })); 6054 }, 6055 className: "w-full bg-black border border-zinc-700 rounded p-1 text-white text-[10px]" 6056 } 6057 ) 6058 ] }) 6059 ] }), 6060 ((_n2 = (_m2 = selectedClothingDef.gluing) == null ? void 0 : _m2[part]) == null ? void 0 : _n2.type) && ((_p2 = (_o2 = selectedClothingDef.gluing) == null ? void 0 : _o2[part]) == null ? void 0 : _p2.type) !== "none" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-3 text-xs bg-black/40 p-2 rounded mt-2", children: [
6061 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1.5", children: [ 6062 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-[9px] text-zinc-500 font-bold uppercase flex items-center justify-between", children: [ 6063 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Point 1 (Start Anchor)" }), 6064 /* @__PURE__ */ jsxRuntimeExports.jsx( 6065 "button", 6066 { 6067 type: "button", 6068 onClick: () => setEditMode(editMode === `clothing_plug_p1_${part}` ? "none" : `clothing_plug_p1_${part}`), 6069 className: `px-1 py-0.5 rounded text-[8px] border transition-colors ${editMode === `clothing_plug_p1_${part}` ? "bg-emerald-600 border-emerald-500 text-white font-bold" : "bg-zinc-850 border-zinc-700 text-zinc-400 hover:text-white"}`, 6070 children: "ð¯ Pin" 6071 } 6072 ) 6073 ] }),
6074 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-1.5", children: [ 6075 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6076 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[8px] text-zinc-500 uppercase", children: "Local X" }), 6077 /* @__PURE__ */ jsxRuntimeExports.jsx( 6078 "input", 6079 { 6080 type: "number", 6081 value: ((_s2 = (_r2 = (_q2 = selectedClothingDef.gluing) == null ? void 0 : _q2[part]) == null ? void 0 : _r2.p1) == null ? void 0 : _s2.x) ?? 0, 6082 onChange: (e) => { 6083 var _a3; 6084 const val = parseFloat(e.target.value) || 0; 6085 const cg = ((_a3 = selectedClothingDef.gluing) == null ? void 0 : _a3[part]) || { type: "one-point", p1: { x: 0, y: 0 }, p2: { x: 0, y: 40 }, t1: "body", t2: "body" }; 6086 setClothingConfigs((prev) => ({ ...prev, [selectedClothingDef.id]: { ...selectedClothingDef, gluing: { ...selectedClothingDef.gluing, [part]: { ...cg, p1: { ...cg.p1 || { y: 0 }, x: val } } } } })); 6087 }, 6088 className: "w-full bg-black border border-zinc-700 rounded p-0.5 text-white text-[10px]" 6089 } 6090 ) 6091 ] }),
6092 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6093 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[8px] text-zinc-500 uppercase", children: "Local Y" }), 6094 /* @__PURE__ */ jsxRuntimeExports.jsx( 6095 "input", 6096 { 6097 type: "number", 6098 value: ((_v2 = (_u2 = (_t2 = selectedClothingDef.gluing) == null ? void 0 : _t2[part]) == null ? void 0 : _u2.p1) == null ? void 0 : _v2.y) ?? 0, 6099 onChange: (e) => { 6100 var _a3; 6101 const val = parseFloat(e.target.value) || 0; 6102 const cg = ((_a3 = selectedClothingDef.gluing) == null ? void 0 : _a3[part]) || { type: "one-point", p1: { x: 0, y: 0 }, p2: { x: 0, y: 40 }, t1: "body", t2: "body" }; 6103 setClothingConfigs((prev) => ({ ...prev, [selectedClothingDef.id]: { ...selectedClothingDef, gluing: { ...selectedClothingDef.gluing, [part]: { ...cg, p1: { ...cg.p1 || { x: 0 }, y: val } } } } })); 6104 }, 6105 className: "w-full bg-black border border-zinc-700 rounded p-0.5 text-white text-[10px]" 6106 } 6107 ) 6108 ] }) 6109 ] }),
6110 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6111 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[8px] text-zinc-500 uppercase", children: "Target Joint 1" }), 6112 /* @__PURE__ */ jsxRuntimeExports.jsx( 6113 "select", 6114 { 6115 value: ((_x2 = (_w2 = selectedClothingDef.gluing) == null ? void 0 : _w2[part]) == null ? void 0 : _x2.t1) || "body", 6116 onChange: (e) => { 6117 var _a3; 6118 const val = e.target.value; 6119 const cg = ((_a3 = selectedClothingDef.gluing) == null ? void 0 : _a3[part]) || { type: "one-point", p1: { x: 0, y: 0 }, p2: { x: 0, y: 40 }, t1: "body", t2: "body" }; 6120 setClothingConfigs((prev) => ({ ...prev, [selectedClothingDef.id]: { ...selectedClothingDef, gluing: { ...selectedClothingDef.gluing, [part]: { ...cg, t1: val } } } })); 6121 }, 6122 className: "w-full bg-black border border-zinc-700 rounded p-0.5 text-white text-[10px]", 6123 children: ["head", "body", "shoulder_l", "elbow_l", "hand_l", "shoulder_r", "elbow_r", "hand_r", "leg_l", "foot_l", "leg_r", "foot_r"].map((j) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: j, children: j }, j)) 6124 } 6125 ) 6126 ] }) 6127 ] }), 6128 ((_z2 = (_y2 = selectedClothingDef.gluing) == null ? void 0 : _y2[part]) == null ? void 0 : _z2.type) === "two-point" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1.5 border-l border-zinc-800 pl-3", children: [
6129 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-[9px] text-zinc-500 font-bold uppercase flex items-center justify-between", children: [ 6130 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Point 2 (End Anchor)" }), 6131 /* @__PURE__ */ jsxRuntimeExports.jsx( 6132 "button", 6133 { 6134 type: "button", 6135 onClick: () => setEditMode(editMode === `clothing_plug_p2_${part}` ? "none" : `clothing_plug_p2_${part}`), 6136 className: `px-1 py-0.5 rounded text-[8px] border transition-colors ${editMode === `clothing_plug_p2_${part}` ? "bg-emerald-600 border-emerald-500 text-white font-bold" : "bg-zinc-850 border-zinc-700 text-zinc-400 hover:text-white"}`, 6137 children: "ð¯ Pin" 6138 } 6139 ) 6140 ] }),
6141 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-1.5", children: [ 6142 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6143 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[8px] text-zinc-500 uppercase", children: "Local X" }), 6144 /* @__PURE__ */ jsxRuntimeExports.jsx( 6145 "input", 6146 { 6147 type: "number", 6148 value: ((_C2 = (_B2 = (_A2 = selectedClothingDef.gluing) == null ? void 0 : _A2[part]) == null ? void 0 : _B2.p2) == null ? void 0 : _C2.x) ?? 0, 6149 onChange: (e) => { 6150 var _a3; 6151 const val = parseFloat(e.target.value) || 0; 6152 const cg = ((_a3 = selectedClothingDef.gluing) == null ? void 0 : _a3[part]) || { type: "two-point", p1: { x: 0, y: 0 }, p2: { x: 0, y: 40 }, t1: "body", t2: "body" }; 6153 setClothingConfigs((prev) => ({ ...prev, [selectedClothingDef.id]: { ...selectedClothingDef, gluing: { ...selectedClothingDef.gluing, [part]: { ...cg, p2: { ...cg.p2 || { y: 0 }, x: val } } } } })); 6154 }, 6155 className: "w-full bg-black border border-zinc-700 rounded p-0.5 text-white text-[10px]" 6156 } 6157 ) 6158 ] }),
6159 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6160 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[8px] text-zinc-500 uppercase", children: "Local Y" }), 6161 /* @__PURE__ */ jsxRuntimeExports.jsx( 6162 "input", 6163 { 6164 type: "number", 6165 value: ((_F2 = (_E2 = (_D2 = selectedClothingDef.gluing) == null ? void 0 : _D2[part]) == null ? void 0 : _E2.p2) == null ? void 0 : _F2.y) ?? 40, 6166 onChange: (e) => { 6167 var _a3; 6168 const val = parseFloat(e.target.value) || 0; 6169 const cg = ((_a3 = selectedClothingDef.gluing) == null ? void 0 : _a3[part]) || { type: "two-point", p1: { x: 0, y: 0 }, p2: { x: 0, y: 40 }, t1: "body", t2: "body" }; 6170 setClothingConfigs((prev) => ({ ...prev, [selectedClothingDef.id]: { ...selectedClothingDef, gluing: { ...selectedClothingDef.gluing, [part]: { ...cg, p2: { ...cg.p2 || { x: 0 }, y: val } } } } })); 6171 }, 6172 className: "w-full bg-black border border-zinc-700 rounded p-0.5 text-white text-[10px]" 6173 } 6174 ) 6175 ] }) 6176 ] }),
6177 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6178 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[8px] text-zinc-500 uppercase", children: "Target Joint 2" }), 6179 /* @__PURE__ */ jsxRuntimeExports.jsx( 6180 "select", 6181 { 6182 value: ((_H2 = (_G2 = selectedClothingDef.gluing) == null ? void 0 : _G2[part]) == null ? void 0 : _H2.t2) || "elbow_l", 6183 onChange: (e) => { 6184 var _a3; 6185 const val = e.target.value; 6186 const cg = ((_a3 = selectedClothingDef.gluing) == null ? void 0 : _a3[part]) || { type: "two-point", p1: { x: 0, y: 0 }, p2: { x: 0, y: 40 }, t1: "body", t2: "body" }; 6187 setClothingConfigs((prev) => ({ ...prev, [selectedClothingDef.id]: { ...selectedClothingDef, gluing: { ...selectedClothingDef.gluing, [part]: { ...cg, t2: val } } } })); 6188 }, 6189 className: "w-full bg-black border border-zinc-700 rounded p-0.5 text-white text-[10px]", 6190 children: ["head", "body", "shoulder_l", "elbow_l", "hand_l", "shoulder_r", "elbow_r", "hand_r", "leg_l", "foot_l", "leg_r", "foot_r"].map((j) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: j, children: j }, j)) 6191 } 6192 ) 6193 ] }) 6194 ] }) 6195 ] }) 6196 ] }),
6197 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-4 mt-2", children: [ 6198 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx( 6199 "textarea", 6200 { 6201 value: selectedClothingDef.parts[part] || "", 6202 onChange: (e) => { 6203 setClothingConfigs((prev) => ({ 6204 ...prev, 6205 [selectedClothingDef.id]: { 6206 ...selectedClothingDef, 6207 parts: { 6208 ...selectedClothingDef.parts, 6209 [part]: e.target.value 6210 } 6211 } 6212 })); 6213 }, 6214 placeholder: `<g class="clothing-${selectedClothingDef.id}-${part}">...</g>`, 6215 className: "w-full h-24 bg-zinc-900 border border-zinc-700 rounded p-2 text-emerald-400 font-mono text-xs resize-y" 6216 } 6217 ) }),
6218 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-32 h-32 bg-zinc-900 border border-zinc-700 rounded flex items-center justify-center overflow-hidden shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { viewBox: "-50 -50 100 100", className: "w-full h-full overflow-visible", children: /* @__PURE__ */ jsxRuntimeExports.jsx("g", { dangerouslySetInnerHTML: { __html: selectedClothingDef.parts[part] || "" } }) }) }) 6219 ] }) 6220 ] }) 6221 ] 6222 }, 6223 part 6224 ); 6225 }) 6226 ] }) 6227 ] }) 6228 ] }), 6229 activeTab === "actions" && (() => { 6230 const selAction = globalActions.find((a) => a.id === selectedActionId) || null; 6231 const selRule = globalActionRules.find((r) => r.id === selectedActionRuleId) || null; 6232 if (selAction) { 6233 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [
6234 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4", children: [ 6235 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { value: selAction.name, onChange: (e) => updateBodyAction(selAction.id, { name: e.target.value }), className: "text-xl font-bold bg-transparent border-b border-zinc-700 text-white focus:outline-none focus:border-indigo-500 flex-1" }), 6236 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs bg-zinc-800 text-zinc-400 px-2 py-1 rounded font-mono", children: selAction.id }) 6237 ] }), 6238 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [ 6239 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6240 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs text-zinc-500 block mb-1", children: "Trigger" }), 6241 /* @__PURE__ */ jsxRuntimeExports.jsxs("select", { value: selAction.trigger, onChange: (e) => updateBodyAction(selAction.id, { trigger: e.target.value }), className: "w-full bg-zinc-800 border border-zinc-700 text-zinc-200 text-sm rounded px-2 py-1.5", children: [
6242 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "playing", children: "While Playing" }), 6243 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "idle", children: "While Idle" }), 6244 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "note_on", children: "On Note" }), 6245 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "always", children: "Always" }) 6246 ] }) 6247 ] }), 6248 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6249 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs text-zinc-500 block mb-1", children: "Priority" }), 6250 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "number", value: selAction.priority, onChange: (e) => updateBodyAction(selAction.id, { priority: parseInt(e.target.value) || 0 }), className: "w-full bg-zinc-800 border border-zinc-700 text-zinc-200 text-sm rounded px-2 py-1.5" }) 6251 ] }),
6252 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6253 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs text-zinc-500 block mb-1", children: "Duration (ms)" }), 6254 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "number", value: selAction.duration, onChange: (e) => updateBodyAction(selAction.id, { duration: parseInt(e.target.value) || 0 }), className: "w-full bg-zinc-800 border border-zinc-700 text-zinc-200 text-sm rounded px-2 py-1.5" }) 6255 ] }), 6256 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [ 6257 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-sm text-zinc-300 cursor-pointer", children: [ 6258 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "checkbox", checked: selAction.loop, onChange: (e) => updateBodyAction(selAction.id, { loop: e.target.checked }), className: "accent-indigo-500" }), 6259 "Loop" 6260 ] }),
6261 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-sm text-zinc-300 cursor-pointer", children: [ 6262 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "checkbox", checked: selAction.tempoSync, onChange: (e) => updateBodyAction(selAction.id, { tempoSync: e.target.checked }), className: "accent-indigo-500" }), 6263 "Tempo Sync" 6264 ] }) 6265 ] }), 6266 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6267 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs text-zinc-500 block mb-1", children: "Blend In (ms)" }), 6268 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "number", value: selAction.blendInMs, onChange: (e) => updateBodyAction(selAction.id, { blendInMs: parseInt(e.target.value) || 0 }), className: "w-full bg-zinc-800 border border-zinc-700 text-zinc-200 text-sm rounded px-2 py-1.5" }) 6269 ] }), 6270 /* @__P
6270URE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6271 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs text-zinc-500 block mb-1", children: "Blend Out (ms)" }), 6272 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "number", value: selAction.blendOutMs, onChange: (e) => updateBodyAction(selAction.id, { blendOutMs: parseInt(e.target.value) || 0 }), className: "w-full bg-zinc-800 border border-zinc-700 text-zinc-200 text-sm rounded px-2 py-1.5" }) 6273 ] }) 6274 ] }), 6275 selAction.trigger === "idle" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-4 bg-zinc-900 rounded p-4", children: [ 6276 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6277 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs text-zinc-500 block mb-1", children: "Min Interval (ms)" }), 6278 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "number", value: selAction.minInterval ?? 1e4, onChange: (e) => updateBodyAction(selAction.id, { minInterval: parseInt(e.target.value) || 0 }), className: "w-full bg-zinc-800 border border-zinc-700 text-zinc-200 text-sm rounded px-2 py-1.5" }) 6279 ] }),
6280 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6281 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs text-zinc-500 block mb-1", children: "Max Interval (ms)" }), 6282 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "number", value: selAction.maxInterval ?? 3e4, onChange: (e) => updateBodyAction(selAction.id, { maxInterval: parseInt(e.target.value) || 0 }), className: "w-full bg-zinc-800 border border-zinc-700 text-zinc-200 text-sm rounded px-2 py-1.5" }) 6283 ] }) 6284 ] }), 6285 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6286 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between mb-3", children: [ 6287 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-sm font-bold text-zinc-300 uppercase tracking-widest", children: "Channels" }), 6288 /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { onClick: () =>
6288 addBodyChannel(selAction.id), className: "text-xs text-indigo-400 hover:text-indigo-300 flex items-center gap-1", children: [ 6289 /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { size: 12 }), 6290 " Add Channel" 6291 ] }) 6292 ] }), 6293 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [ 6294 selAction.channels.map((ch, idx) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `border rounded-lg overflow-hidden ${selectedChannelIdx === idx ? "border-indigo-500" : "border-zinc-800"}`, children: [ 6295 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { onClick: () => setSelectedChannelIdx(selectedChannelIdx === idx ? null : idx), className: "flex items-center justify-between px-4 py-2.5 bg-zinc-900 cursor-pointer hover:bg-zinc-800", children: [ 6296 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [ 6297 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-xs px-2 py-0.5 rounded font-mono ${ch.type === "wave" ? "bg-blue-900 text-blue-300" : "bg-purple-900 text-purple-300"}`, children: ch.type }),
6298 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-sm text-zinc-200 font-medium", children: [ 6299 " ", 6300 ch.joint 6301 ] }), 6302 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-sm text-zinc-500", children: [ 6303 ".", 6304 ch.property 6305 ] }) 6306 ] }), 6307 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 6308 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: (e) => { 6309 e.stopPropagation(); 6310 removeBodyChannel(selAction.id, idx); 6311 }, className: "p-1 text-zinc-600 hover:text-red-400", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 12 }) }), 6312 selectedChannelIdx === idx ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { size: 14, className: "text-zinc-500" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 14, className: "text-zinc-500" }) 6313 ] }) 6314 ] }), 6315 selectedChannelIdx === idx && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 bg-zinc-950 space-y-4", children: [
6316 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-3 gap-3", children: [ 6317 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6318 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs text-zinc-500 block mb-1", children: "Joint" }), 6319 /* @__PURE__ */ jsxRuntimeExports.jsx("select", { value: ch.joint, onChange: (e) => updateBodyChannel(selAction.id, idx, { joint: e.target.value }), className: "w-full bg-zinc-800 border border-zinc-700 text-zinc-200 text-sm rounded px-2 py-1.5", children: JOINTS_LIST.map((j) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: j, children: j }, j)) }) 6320 ] }), 6321 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6322 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs text-zinc-500 block mb-1", children: "Property" }), 6323 /* @__PURE__ */ jsxRuntimeExports.jsxs("select", { value: ch.property, onChange: (e) => updateBodyChannel(selAction.id, idx, { property: e.target.value }), className: "w-full bg-zinc-800 border border-zinc-700 text-zinc-200 text-sm rounded px-2 py-1.5", children: [
6324 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "x", children: "x" }), 6325 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "y", children: "y" }), 6326 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "rotation", children: "rotation" }) 6327 ] }) 6328 ] }), 6329 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6330 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs text-zinc-500 block mb-1", children: "Type" }), 6331 /* @__PURE__ */ jsxRuntimeExports.jsxs("select", { value: ch.type, onChange: (e) => updateBodyChannel(selAction.id, idx, { type: e.target.value, keyframes: e.target.value === "keyframe" ? [{ time: 0, value: 0, easing: "sine" }, { time: 1, value: 0, easing: "sine" }] : void 0 }), className: "w-full bg-zinc-800 border border-zinc-700 text-zinc-200 text-sm rounded px-2 py-1.5", children: [
6332 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "wave", children: "Wave (formula)" }), 6333 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "keyframe", children: "Keyframe" }) 6334 ] }) 6335 ] }) 6336 ] }), 6337 ch.type === "wave" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-3 gap-3", children: [ 6338 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6339 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs text-zinc-500 block mb-1", children: "Waveform" }), 6340 /* @__PURE__ */ jsxRuntimeExports.jsx("select", { value: ch.waveform ?? "sine", onChange: (e) => updateBodyChannel(selAction.id, idx, { waveform: e.target.value }), className: "w-full bg-zinc-800 border border-zinc-700 text-zinc-200 text-sm rounded px-2 py-1.5", children: Object.entries(WAVEFORM_LABELS_MAP).map(([v, l]) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: v, children: l }, v)) }) 6341 ] }),
6342 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6343 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "text-xs text-zinc-500 block mb-1", children: [ 6344 "Frequency (", 6345 selAction.tempoSync ? "cycles/beat" : "Hz", 6346 ")" 6347 ] }), 6348 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "number", step: "0.01", value: ch.frequency ?? 1, onChange: (e) => updateBodyChannel(selAction.id, idx, { frequency: parseFloat(e.target.value) || 0 }), className: "w-full bg-zinc-800 border border-zinc-700 text-zinc-200 text-sm rounded px-2 py-1.5" }) 6349 ] }), 6350 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6351 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs text-zinc-500 block mb-1", children: "Phase (rad)" }), 6352 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "number", step: "0.01", value: ch.phase ?? 0, onChange: (e) => updateBodyChannel(selAction.id, idx, { phase: parseFloat(e.target.value) || 0 }), className: "w-full bg-zinc-800 border border-zinc-700 text-zinc-200 text-sm rounded px-2 py-1.5" }) 6353 ] }),
6354 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6355 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs text-zinc-500 block mb-1", children: "Amplitude Base" }), 6356 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "number", step: "0.1", value: ch.amplitudeBase ?? 0, onChange: (e) => updateBodyChannel(selAction.id, idx, { amplitudeBase: parseFloat(e.target.value) || 0 }), className: "w-full bg-zinc-800 border border-zinc-700 text-zinc-200 text-sm rounded px-2 py-1.5" }) 6357 ] }), 6358 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6359 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs text-zinc-500 block mb-1", children: "Amplitude (Ãintensity)" }), 6360 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "number", step: "0.1", value: ch.amplitude ?? 1, onChange: (e) => updateBodyChannel(selAction.id, idx, { amplitude: parseFloat(e.target.value) || 0 }), className: "w-full bg-zinc-800 border border-zinc-700 text-zinc-200 text-sm rounded px-2 py-1.5" }) 6361 ] }),
6362 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6363 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs text-zinc-500 block mb-1", children: "Intensity Scale" }), 6364 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "number", step: "0.1", min: "0", max: "1", value: ch.intensityScale ?? 0, onChange: (e) => updateBodyChannel(selAction.id, idx, { intensityScale: parseFloat(e.target.value) || 0 }), className: "w-full bg-zinc-800 border border-zinc-700 text-zinc-200 text-sm rounded px-2 py-1.5" }) 6365 ] }), 6366 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6367 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs text-zinc-500 block mb-1", children: "Offset" }), 6368 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "number", step: "0.1", value: ch.offset ?? 0, onChange: (e) => updateBodyChannel(selAction.id, idx, { offset: parseFloat(e.target.value) || 0 }), className: "w-full bg-zinc-800 border border-zinc-700 text-zinc-200 text-sm rounded px-2 py-1.5" }) 6369 ] }),
6370 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "col-span-2 flex items-end", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs text-zinc-600 font-mono p-2 bg-zinc-900 rounded w-full", children: [ 6371 "result = ", 6372 ch.waveform ?? "sin", 6373 "(t à ", 6374 ch.frequency ?? 1, 6375 ") à (", 6376 ch.amplitudeBase ?? 0, 6377 " + ", 6378 ch.amplitude ?? 1, 6379 " à intensity) + ", 6380 ch.offset ?? 0 6381 ] }) }) 6382 ] }), 6383 ch.type === "keyframe" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [ 6384 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [ 6385 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-zinc-500", children: "Keyframes" }),
6386 /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { onClick: () => addBodyKeyframe(selAction.id, idx), className: "text-xs text-indigo-400 hover:text-indigo-300 flex items-center gap-1", children: [ 6387 /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { size: 10 }), 6388 " Add" 6389 ] }) 6390 ] }), 6391 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative h-16 bg-zinc-900 rounded overflow-hidden border border-zinc-800", children: [ 6392 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-x-2 top-1/2 h-px bg-zinc-700" }), 6393 (ch.keyframes || []).map((kf, ki) => { 6394 const maxV = Math.max(20, ...(ch.keyframes || []).map((k) => Math.abs(k.value))); 6395 return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute w-3 h-3 rounded-full bg-indigo-400 border-2 border-white -translate-x-1/2 -translate-y-1/2 cursor-pointer", style: { left: `${kf.time * 100}%`, top: `${50 - kf.value / maxV * 45}%` }, title: `t=${kf.time}, v=${kf.value}` }, ki); 6396 }) 6397 ] }),
6398 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "max-h-40 overflow-y-auto space-y-1", children: (ch.keyframes || []).map((kf, ki) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-xs", children: [ 6399 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-600 w-4", children: ki }), 6400 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-zinc-500", children: "t=" }), 6401 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "number", step: "0.01", min: "0", max: "1", value: kf.time, onChange: (e) => { 6402 const nk = [...ch.keyframes || []]; 6403 nk[ki] = { ...nk[ki], time: parseFloat(e.target.value) || 0 }; 6404 updateBodyChannel(selAction.id, idx, { keyframes: nk.sort((a, b) => a.time - b.time) }); 6405 }, className: "w-16 bg-zinc-800 border border-zinc-700 text-zinc-200 rounded px-1 py-0.5" }),
6406 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-zinc-500", children: "v=" }), 6407 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "number", step: "0.1", value: kf.value, onChange: (e) => { 6408 const nk = [...ch.keyframes || []]; 6409 nk[ki] = { ...nk[ki], value: parseFloat(e.target.value) || 0 }; 6410 updateBodyChannel(selAction.id, idx, { keyframes: nk }); 6411 }, className: "w-16 bg-zinc-800 border border-zinc-700 text-zinc-200 rounded px-1 py-0.5" }), 6412 /* @__PURE__ */ jsxRuntimeExports.jsxs("select", { value: kf.easing ?? "sine", onChange: (e) => { 6413 const nk = [...ch.keyframes || []]; 6414 nk[ki] = { ...nk[ki], easing: e.target.value }; 6415 updateBodyChannel(selAction.id, idx, { keyframes: nk }); 6416 }, className: "flex-1 bg-zinc-800 border border-zinc-700 text-zinc-200 rounded px-1 py-0.5", children: [
6417 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "linear", children: "Linear" }), 6418 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "sine", children: "Sine" }), 6419 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "ease-in-out", children: "Ease In-Out" }) 6420 ] }), 6421 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => { 6422 updateBodyChannel(selAction.id, idx, { keyframes: (ch.keyframes || []).filter((_, i) => i !== ki) }); 6423 }, className: "text-zinc-600 hover:text-red-400", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 10 }) }) 6424 ] }, ki)) }) 6425 ] }) 6426 ] }) 6427 ] }, idx)), 6428 selAction.channels.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-center py-8 text-zinc-600 text-sm border border-dashed border-zinc-800 rounded-lg", children: "No channels yet. Add a channel to animate a joint." }) 6429 ] }) 6430 ] }) 6431 ] }); 6432 } else if (selRule) { 6433 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [
6434 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4", children: [ 6435 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { value: selRule.name, onChange: (e) => updateBodyRule(selRule.id, { name: e.target.value }), className: "text-xl font-bold bg-transparent border-b border-zinc-700 text-white focus:outline-none focus:border-indigo-500 flex-1" }), 6436 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-sm text-zinc-300", children: [ 6437 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "checkbox", checked: selRule.enabled, onChange: (e) => updateBodyRule(selRule.id, { enabled: e.target.checked }), className: "accent-indigo-500" }), 6438 "Enabled" 6439 ] }) 6440 ] }), 6441 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [ 6442 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6443 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs text-zinc-500 block mb-2", children: "Applies To" }),
6444 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-2", children: ["all", "family", "selected"].map((scope) => /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-sm text-zinc-300 cursor-pointer", children: [ 6445 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "radio", name: `scope-${selRule.id}`, value: scope, checked: selRule.condition.scope === scope, onChange: () => updateBodyRule(selRule.id, { condition: { ...selRule.condition, scope } }), className: "accent-indigo-500" }), 6446 scope === "all" ? "All Characters" : scope === "family" ? "Specific Families" : "Selected Characters" 6447 ] }, scope)) }), 6448 selRule.condition.scope === "family" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-3 space-y-1", children: ["percussion", "keys", "strings", "winds", "other"].map((fam) => /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-sm text-zinc-400 cursor-pointer", children: [
6449 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "checkbox", checked: (selRule.condition.families || []).includes(fam), onChange: (e) => { 6450 const f = selRule.condition.families || []; 6451 updateBodyRule(selRule.id, { condition: { ...selRule.condition, families: e.target.checked ? [...f, fam] : f.filter((x) => x !== fam) } }); 6452 }, className: "accent-indigo-500" }), 6453 fam.charAt(0).toUpperCase() + fam.slice(1) 6454 ] }, fam)) }), 6455 selRule.condition.scope === "selected" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-3", children: [ 6456 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs text-zinc-500 block mb-1", children: "Character IDs (comma separated)" }), 6457 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "text", value: (selRule.condition.characterIds || []).join(", "), onChange: (e) => updateBodyRule(selRule.id, { condition: { ...selRule.condition, characterIds: e.target.value.split(",").map((s) => s.trim()).filter(Boolean) } }), className: "w-full bg-zinc-800 border border-zinc-700 text-zinc-200 text-sm rounded px-2 py-1.5", placeholder: "e.g. family_5_strings" }) 6458 ] }) 6459 ] }),
6460 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6461 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs text-zinc-500 block mb-2", children: "Actions to Apply" }), 6462 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1 max-h-64 overflow-y-auto", children: globalActions.map((action) => /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-sm text-zinc-300 cursor-pointer p-1.5 rounded hover:bg-zinc-800", children: [ 6463 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "checkbox", checked: selRule.actionIds.includes(action.id), onChange: (e) => { 6464 const ids = selRule.actionIds; 6465 updateBodyRule(selRule.id, { actionIds: e.target.checked ? [...ids, action.id] : ids.filter((id) => id !== action.id) }); 6466 }, className: "accent-indigo-500" }), 6467 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium", children: action.name }),
6468 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-zinc-600 text-xs", children: [ 6469 "(", 6470 TRIGGER_LABELS_MAP[action.trigger], 6471 ")" 6472 ] }) 6473 ] }, action.id)) }) 6474 ] }) 6475 ] }) 6476 ] }); 6477 } else { 6478 return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-full flex items-center justify-center text-zinc-600 text-sm py-12", children: "Select an action or rule to edit it." }); 6479 } 6480 })(), 6481 activeTab === "background" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [ 6482 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6483 /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-lg font-bold text-white mb-1", children: selectedPresetId === null ? "Global Default Background" : `Preset: ${(_w = backgroundPresets.find((p) => p.id === selectedPresetId)) == null ? void 0 : _w.name}` }),
6484 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-500 font-sans", children: selectedPresetId === null ? "Configure the global background used when no track tags match any presets." : "Configure this preset's background settings and matching tags." }) 6485 ] }), 6486 selectedPresetId !== null && (() => { 6487 const preset = backgroundPresets.find((p) => p.id === selectedPresetId); 6488 if (!preset) return null; 6489 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-950 border border-zinc-800 p-4 rounded-lg space-y-2 animate-fadeIn", children: [ 6490 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs text-zinc-300 font-bold uppercase tracking-wider block", children: "Matching Tags (comma separated)" }), 6491 /* @__PURE__ */ jsxRuntimeExports.jsx( 6492 "input", 6493 { 6494 type: "text", 6495 value: preset.tags.join(", "), 6496 onChange: (e) => { 6497 const newTags = e.target.value.split(",").map((t) => t.trim()).filter(Boolean); 6498 pushUndo(); 6499 setBackgroundPresets((prev) => prev.map((p) => { 6500 if (p.id === selectedPresetId) { 6501 return { ...p, tags: newTags }; 6502 } 6503 return p; 6504 })); 6505 }, 6506 className: "w-full bg-zinc-900 border border-zinc-700 text-zinc-300 text-xs px-2.5 py-1.5 rounded", 6507 placeholder: "e.g. classical, orchestra, piano" 6508 } 6509 ),
6510 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-zinc-500 font-sans", children: "Tracks with tags matching any of these terms will automatically load this background." }) 6511 ] }); 6512 })(), 6513 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [ 6514 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs text-zinc-400 font-bold uppercase tracking-wider block", children: "Background Type" }), 6515 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-3 gap-1.5 bg-zinc-950 p-1 rounded border border-zinc-800", children: ["color", "gradient", "image"].map((type) => /* @__PURE__ */ jsxRuntimeExports.jsx( 6516 "button", 6517 { 6518 onClick: () => { 6519 updateActiveConfig((prev) => ({ ...prev, type })); 6520 }, 6521 className: `py-1.5 text-xs font-bold rounded capitalize transition-all ${currentConfigToEdit.type === type ? "bg-indigo-600 text-white shadow-md" : "text-zinc-400 hover:text-zinc-200"}`, 6522 children: type 6523 }, 6524 type 6525 )) }) 6526 ] }), 6527 currentConfigToEdit.type === "color" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "bg-zinc-950 border border-zinc-800 p-4 rounded-lg space-y-3", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
6528 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs text-zinc-300 font-medium", children: "Solid Background Color" }), 6529 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 6530 /* @__PURE__ */ jsxRuntimeExports.jsx( 6531 "input", 6532 { 6533 type: "color", 6534 value: currentConfigToEdit.color || "#0c0c14", 6535 onChange: (e) => { 6536 updateActiveConfig((prev) => ({ ...prev, color: e.target.value })); 6537 }, 6538 className: "w-8 h-8 rounded border border-zinc-700 bg-transparent cursor-pointer" 6539 } 6540 ), 6541 /* @__PURE__ */ jsxRuntimeExports.jsx( 6542 "input", 6543 { 6544 type: "text", 6545 value: currentConfigToEdit.color || "#0c0c14", 6546 onChange: (e) => { 6547 updateActiveConfig((prev) => ({ ...prev, color: e.target.value })); 6548 }, 6549 className: "w-24 bg-zinc-900 border border-zinc-700 text-zinc-300 text-xs px-2 py-1.5 rounded font-mono" 6550 } 6551 ) 6552 ] }) 6553 ] }) }), 6554 currentConfigToEdit.type === "gradient" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-950 border border-zinc-800 p-4 rounded-lg space-y-4", children: [
6555 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-3", children: [ 6556 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6557 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] text-zinc-400 font-bold uppercase tracking-wider block mb-1", children: "Shape" }), 6558 /* @__PURE__ */ jsxRuntimeExports.jsxs( 6559 "select", 6560 { 6561 value: ((_x = currentConfigToEdit.gradient) == null ? void 0 : _x.type) || "linear", 6562 onChange: (e) => { 6563 updateActiveConfig((prev) => ({ 6564 ...prev, 6565 gradient: { ...prev.gradient, type: e.target.value } 6566 })); 6567 }, 6568 className: "w-full bg-zinc-900 border border-zinc-700 rounded p-1.5 text-zinc-300 text-xs", 6569 children: [ 6570 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "linear", children: "Linear" }),
6571 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "radial", children: "Radial" }) 6572 ] 6573 } 6574 ) 6575 ] }), 6576 ((_y = currentConfigToEdit.gradient) == null ? void 0 : _y.type) === "linear" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6577 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "text-[10px] text-zinc-400 font-bold uppercase tracking-wider block mb-1", children: [ 6578 "Angle (", 6579 ((_z = currentConfigToEdit.gradient) == null ? void 0 : _z.angle) || 0, 6580 "°)" 6581 ] }), 6582 /* @__PURE__ */ jsxRuntimeExports.jsx( 6583 "input", 6584 { 6585 type: "range", 6586 min: "0", 6587 max: "360", 6588 value: ((_A = currentConfigToEdit.gradient) == null ? void 0 : _A.angle) || 0, 6589 onChange: (e) => { 6590 updateActiveConfig((prev) => ({ 6591 ...prev, 6592 gradient: { ...prev.gradient, angle: parseInt(e.target.value) || 0 } 6593 })); 6594 }, 6595 className: "w-full accent-indigo-500 h-1.5 rounded-lg bg-zinc-800 cursor-pointer" 6596 } 6597 ) 6598 ] }) 6599 ] }),
6600 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [ 6601 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [ 6602 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs text-zinc-300 font-medium", children: "Color Stops" }), 6603 /* @__PURE__ */ jsxRuntimeExports.jsxs( 6604 "button", 6605 { 6606 onClick: () => { 6607 updateActiveConfig((prev) => { 6608 var _a2; 6609 const stops = ((_a2 = prev.gradient) == null ? void 0 : _a2.stops) || []; 6610 const newOffset = stops.length > 0 ? Math.min(100, stops[stops.length - 1].offset + 20) : 50; 6611 return { 6612 ...prev, 6613 gradient: { 6614 ...prev.gradient, 6615 stops: [...stops, { color: "#4f46e5", offset: newOffset }] 6616 } 6617 }; 6618 }); 6619 }, 6620 className: "text-xs text-indigo-400 hover:text-indigo-300 flex items-center gap-1 font-bold", 6621 children: [
6622 /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { size: 12 }), 6623 " Add Stop" 6624 ] 6625 } 6626 ) 6627 ] }), 6628 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-2 max-h-48 overflow-y-auto pr-1", children: (_C = (_B = currentConfigToEdit.gradient) == null ? void 0 : _B.stops) == null ? void 0 : _C.map((stop, idx) => { 6629 var _a2; 6630 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between bg-zinc-900 border border-zinc-850 p-2 rounded gap-2 animate-fadeIn", children: [ 6631 /* @__PURE__ */ jsxRuntimeExports.jsx( 6632 "input", 6633 { 6634 type: "color", 6635 value: stop.color, 6636 onChange: (e) => { 6637 updateActiveConfig((prev) => { 6638 var _a3; 6639 const stops = [...((_a3 = prev.gradient) == null ? void 0 : _a3.stops) || []]; 6640 stops[idx] = { ...stops[idx], color: e.target.value }; 6641 return { ...prev, gradient: { ...prev.gradient, stops } }; 6642 }); 6643 }, 6644 className: "w-6 h-6 rounded border border-zinc-700 bg-transparent cursor-pointer" 6645 } 6646 ),
6647 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex items-center gap-2", children: [ 6648 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] text-zinc-500 font-mono w-8", children: [ 6649 stop.offset, 6650 "%" 6651 ] }), 6652 /* @__PURE__ */ jsxRuntimeExports.jsx( 6653 "input", 6654 { 6655 type: "range", 6656 min: "0", 6657 max: "100", 6658 value: stop.offset, 6659 onChange: (e) => { 6660 updateActiveConfig((prev) => { 6661 var _a3; 6662 const stops = [...((_a3 = prev.gradient) == null ? void 0 : _a3.stops) || []]; 6663 stops[idx] = { ...stops[idx], offset: parseInt(e.target.value) || 0 }; 6664 return { ...prev, gradient: { ...prev.gradient, stops } }; 6665 }); 6666 }, 6667 className: "flex-1 accent-indigo-500 h-1 rounded-lg bg-zinc-800 cursor-pointer" 6668 } 6669 ) 6670 ] }),
6671 /* @__PURE__ */ jsxRuntimeExports.jsx( 6672 "button", 6673 { 6674 onClick: () => { 6675 updateActiveConfig((prev) => { 6676 var _a3; 6677 const stops = (((_a3 = prev.gradient) == null ? void 0 : _a3.stops) || []).filter((_, i) => i !== idx); 6678 return { ...prev, gradient: { ...prev.gradient, stops } }; 6679 }); 6680 }, 6681 disabled: (((_a2 = currentConfigToEdit.gradient) == null ? void 0 : _a2.stops) || []).length <= 2, 6682 className: "text-zinc-500 hover:text-red-400 disabled:opacity-30 disabled:hover:text-zinc-500 transition-colors", 6683 children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 12 }) 6684 } 6685 ) 6686 ] }, idx); 6687 }) }) 6688 ] }) 6689 ] }), 6690 currentConfigToEdit.type === "image" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-950 border border-zinc-800 p-4 rounded-lg space-y-4", children: [
6691 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1", children: [ 6692 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] text-zinc-400 font-bold uppercase tracking-wider block", children: "Image URL / Path" }), 6693 /* @__PURE__ */ jsxRuntimeExports.jsx( 6694 "input", 6695 { 6696 type: "text", 6697 value: ((_D = currentConfigToEdit.image) == null ? void 0 : _D.url) || "", 6698 onChange: (e) => { 6699 updateActiveConfig((prev) => ({ 6700 ...prev, 6701 image: { ...prev.image, url: e.target.value } 6702 })); 6703 }, 6704 className: "w-full bg-zinc-900 border border-zinc-700 text-zinc-350 text-xs px-2.5 py-1.5 rounded", 6705 placeholder: "e.g. /assets/visualizers/orchestra/background-forest.webp" 6706 } 6707 ) 6708 ] }), 6709 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2 pt-1 border-t border-zinc-900", children: [
6710 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [ 6711 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] text-zinc-400 font-bold uppercase tracking-wider block", children: "Or Upload Default Background" }), 6712 uploadError && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-red-400 font-sans", children: uploadError }) 6713 ] }), 6714 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 6715 /* @__PURE__ */ jsxRuntimeExports.jsx( 6716 "input", 6717 { 6718 type: "file", 6719 accept: "image/png, image/jpeg, image/jpg, image/webp", 6720 onChange: handleGlobalBgUpload, 6721 className: "hidden", 6722 id: "global-bg-upload-input" 6723 } 6724 ), 6725 /* @__PURE__ */ jsxRuntimeExports.jsxs( 6726 "label", 6727 { 6728 htmlFor: "global-bg-upload-input", 6729 className: "flex items-center gap-1 px-2.5 py-1.5 bg-indigo-600 hover:bg-indigo-750 text-white rounded text-[11px] font-bold cursor-pointer transition-colors shadow-sm", 6730 children: [
6731 /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { size: 12 }), 6732 " Upload Image" 6733 ] 6734 } 6735 ), 6736 ((_E = currentConfigToEdit.image) == null ? void 0 : _E.url) && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] text-zinc-500 font-mono truncate max-w-xs block", children: [ 6737 "Current: ", 6738 currentConfigToEdit.image.url.split("/").pop() 6739 ] }) 6740 ] }) 6741 ] }), 6742 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2 pt-2 border-t border-zinc-900", children: [ 6743 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] text-zinc-400 font-bold uppercase tracking-wider block", children: "Custom Background Options Gallery" }), 6744 customBackgrounds.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-zinc-600 text-[10px] italic p-3 text-center bg-zinc-900/50 rounded border border-zinc-850", children: "No custom backgrounds uploaded yet." }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-3 gap-2.5 max-h-48 overflow-y-auto pr-1", children: customBackgrounds.map((bgUrl) => { 6745 var _a2; 6746 const isSelected = ((_a2 = currentConfigToEdit.image) == null ? void 0 : _a2.url) === bgUrl; 6747 return /* @__PURE__ */ jsxRuntimeExports.jsxs( 6748 "div", 6749 { 6750 className: `relative group rounded overflow-hidden border cursor-pointer aspect-video bg-zinc-950 transition-all ${isSelected ? "border-indigo-500 shadow-[0_0_8px_rgba(99,102,241,0.4)]" : "border-zinc-800 hover:border-zinc-700"}`, 6751 onClick: () => { 6752 updateActiveConfig((prev) => ({ 6753 ...prev, 6754 image: { ...prev.image, url: bgUrl } 6755 })); 6756 }, 6757 children: [
6758 /* @__PURE__ */ jsxRuntimeExports.jsx( 6759 "img", 6760 { 6761 src: bgUrl, 6762 alt: "Custom background", 6763 className: "w-full h-full object-cover" 6764 } 6765 ), 6766 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center gap-1.5", children: [ 6767 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] text-white font-bold bg-zinc-900/80 px-1 rounded", children: isSelected ? "Active" : "Select" }), 6768 /* @__PURE__ */ jsxRuntimeExports.jsx( 6769 "button", 6770 { 6771 onClick: (e) => { 6772 e.stopPropagation(); 6773 handleDeleteGlobalBg(bgUrl); 6774 }, 6775 className: "p-1 bg-red-600/90 text-white rounded hover:bg-red-700 transition-colors shadow", 6776 title: "Delete from options", 6777 children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 10 }) 6778 } 6779 ) 6780 ] }) 6781 ] 6782 }, 6783 bgUrl 6784 ); 6785 }) }) 6786 ] }),
6787 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-3", children: [ 6788 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6789 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] text-zinc-400 font-bold uppercase tracking-wider block mb-1", children: "Image Size" }), 6790 /* @__PURE__ */ jsxRuntimeExports.jsxs( 6791 "select", 6792 { 6793 value: ((_F = currentConfigToEdit.image) == null ? void 0 : _F.size) || "cover", 6794 onChange: (e) => { 6795 updateActiveConfig((prev) => ({ 6796 ...prev, 6797 image: { ...prev.image, size: e.target.value } 6798 })); 6799 }, 6800 className: "w-full bg-zinc-900 border border-zinc-700 rounded p-1.5 text-zinc-300 text-xs", 6801 children: [ 6802 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "cover", children: "Cover (Fill)" }),
6803 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "contain", children: "Contain (Fit)" }), 6804 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "auto", children: "Auto (Original)" }) 6805 ] 6806 } 6807 ) 6808 ] }), 6809 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6810 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "text-[10px] text-zinc-400 font-bold uppercase tracking-wider block mb-1", children: [ 6811 "Blur (", 6812 ((_G = currentConfigToEdit.image) == null ? void 0 : _G.blur) || 0, 6813 "px)" 6814 ] }), 6815 /* @__PURE__ */ jsxRuntimeExports.jsx( 6816 "input", 6817 { 6818 type: "range", 6819 min: "0", 6820 max: "20", 6821 value: ((_H = currentConfigToEdit.image) == null ? void 0 : _H.blur) || 0, 6822 onChange: (e) => { 6823 updateActiveConfig((prev) => ({ 6824 ...prev, 6825 image: { ...prev.image, blur: parseInt(e.target.value) || 0 } 6826 })); 6827 }, 6828 className: "w-full accent-indigo-500 h-1.5 rounded-lg bg-zinc-800 cursor-pointer" 6829 } 6830 ) 6831 ] }) 6832 ] }) 6833 ] }),
6834 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-950 border border-zinc-800 p-4 rounded-lg space-y-4", children: [ 6835 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [ 6836 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col", children: [ 6837 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs text-zinc-200 font-bold uppercase tracking-wider font-sans", children: "Backdrop Overlay Tint" }), 6838 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-zinc-500", children: "Darken or color-tint the background behind musicians." }) 6839 ] }), 6840 /* @__PURE__ */ jsxRuntimeExports.jsx( 6841 "input", 6842 { 6843 type: "checkbox", 6844 checked: ((_I = currentConfigToEdit.overlay) == null ? void 0 : _I.enabled) || false, 6845 onChange: (e) => { 6846 updateActiveConfig((prev) => ({ 6847 ...prev, 6848 overlay: { ...prev.overlay, enabled: e.target.checked } 6849 })); 6850 }, 6851 className: "accent-indigo-500 w-4 h-4 cursor-pointer" 6852 } 6853 ) 6854 ] }), 6855 ((_J = currentConfigToEdit.overlay) == null ? void 0 : _J.enabled) && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-4 pt-2 border-t border-zinc-900 items-center", children: [
6856 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 6857 /* @__PURE__ */ jsxRuntimeExports.jsx( 6858 "input", 6859 { 6860 type: "color", 6861 value: ((_K = currentConfigToEdit.overlay) == null ? void 0 : _K.color) || "#000000", 6862 onChange: (e) => { 6863 updateActiveConfig((prev) => ({ 6864 ...prev, 6865 overlay: { ...prev.overlay, color: e.target.value } 6866 })); 6867 }, 6868 className: "w-6 h-6 rounded border border-zinc-700 bg-transparent cursor-pointer animate-fadeIn" 6869 } 6870 ), 6871 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-zinc-400 font-mono uppercase", children: (_L = currentConfigToEdit.overlay) == null ? void 0 : _L.color }) 6872 ] }), 6873 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
6874 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between text-[10px] text-zinc-500 mb-1", children: [ 6875 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Opacity" }), 6876 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [ 6877 Math.round((((_M = currentConfigToEdit.overlay) == null ? void 0 : _M.opacity) || 0) * 100), 6878 "%" 6879 ] }) 6880 ] }), 6881 /* @__PURE__ */ jsxRuntimeExports.jsx( 6882 "input", 6883 { 6884 type: "range", 6885 min: "0", 6886 max: "1", 6887 step: "0.05", 6888 value: ((_N = currentConfigToEdit.overlay) == null ? void 0 : _N.opacity) || 0, 6889 onChange: (e) => { 6890 updateActiveConfig((prev) => ({ 6891 ...prev, 6892 overlay: { ...prev.overlay, opacity: parseFloat(e.target.value) || 0 } 6893 })); 6894 }, 6895 className: "w-full accent-indigo-500 h-1.5 rounded-lg bg-zinc-800 cursor-pointer" 6896 } 6897 ) 6898 ] }) 6899 ] }) 6900 ] }),
6901 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-950 border border-zinc-800 p-4 rounded-lg space-y-4", children: [ 6902 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [ 6903 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col", children: [ 6904 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs text-zinc-200 font-bold uppercase tracking-wider font-sans", children: "Dynamic Particles" }), 6905 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-zinc-500", children: "Interactive background animations." }) 6906 ] }), 6907 /* @__PURE__ */ jsxRuntimeExports.jsx( 6908 "input", 6909 { 6910 type: "checkbox", 6911 checked: ((_O = currentConfigToEdit.particles) == null ? void 0 : _O.enabled) || false, 6912 onChange: (e) => { 6913 updateActiveConfig((prev) => ({ 6914 ...prev, 6915 particles: { ...prev.particles, enabled: e.target.checked } 6916 })); 6917 }, 6918 className: "accent-indigo-500 w-4 h-4 cursor-pointer" 6919 } 6920 ) 6921 ] }), 6922 ((_P = currentConfigToEdit.particles) == null ? void 0 : _P.enabled) && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3 pt-2 border-t border-zinc-900 animate-fadeIn", children: [
6923 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-3", children: [ 6924 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6925 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] text-zinc-400 font-bold uppercase tracking-wider block mb-1", children: "Particle Style" }), 6926 /* @__PURE__ */ jsxRuntimeExports.jsxs( 6927 "select", 6928 { 6929 value: ((_Q = currentConfigToEdit.particles) == null ? void 0 : _Q.type) || "stars", 6930 onChange: (e) => { 6931 updateActiveConfig((prev) => ({ 6932 ...prev, 6933 particles: { ...prev.particles, type: e.target.value } 6934 })); 6935 }, 6936 className: "w-full bg-zinc-900 border border-zinc-700 rounded p-1.5 text-zinc-350 text-xs", 6937 children: [ 6938 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "stars", children: "Twinkling Stars" }),
6939 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "notes", children: "Floating Notes" }), 6940 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "bubbles", children: "Rising Bubbles" }), 6941 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "snow", children: "Falling Snow" }), 6942 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "rain", children: "Rain" }), 6943 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "confetti", children: "Confetti" }), 6944 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "fireflies", children: "Fireflies" }), 6945 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "hearts", children: "Hearts" }), 6946 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "autumn_leaves", children: "Autumn Leaves" }), 6947 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "matrix", children: "Matrix Code" }),
6948 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "dust", children: "Dust Particles" }), 6949 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "sparkles", children: "Sparkles" }), 6950 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "floating_orbs", children: "Floating Orbs" }), 6951 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "laser_lines", children: "Laser Lines" }) 6952 ] 6953 } 6954 ) 6955 ] }), 6956 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6957 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] text-zinc-400 font-bold uppercase tracking-wider block mb-1", children: "Particle Color" }), 6958 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 mt-0.5", children: [ 6959 /* @__PURE__ */ jsxRuntimeExports.jsx( 6960 "input", 6961 { 6962 type: "color", 6963 value: ((_R = currentConfigToEdit.particles) == null ? void 0 : _R.color) || "#ffffff", 6964 onChange: (e) => { 6965 updateActiveConfig((prev) => ({ 6966 ...prev, 6967 particles: { ...prev.particles, color: e.target.value } 6968 })); 6969 }, 6970 className: "w-6 h-6 rounded border border-zinc-700 bg-transparent cursor-pointer" 6971 } 6972 ),
6973 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-zinc-400 font-mono uppercase", children: (_S = currentConfigToEdit.particles) == null ? void 0 : _S.color }) 6974 ] }) 6975 ] }) 6976 ] }), 6977 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-3 gap-2", children: [ 6978 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 6979 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between text-[10px] text-zinc-500 mb-1", children: [ 6980 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Count" }), 6981 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: ((_T = currentConfigToEdit.particles) == null ? void 0 : _T.count) || 50 }) 6982 ] }), 6983 /* @__PURE__ */ jsxRuntimeExports.jsx( 6984 "input", 6985 { 6986 type: "range", 6987 min: "10", 6988 max: "200", 6989 step: "5", 6990 value: ((_U = currentConfigToEdit.particles) == null ? void 0 : _U.count) || 50, 6991 onChange: (e) => { 6992 updateActiveConfig((prev) => ({ 6993 ...prev, 6994 particles: { ...prev.particles, count: parseInt(e.target.value) || 50 } 6995 })); 6996 }, 6997 className: "w-full accent-indigo-500 h-1.5 rounded-lg bg-zinc-800 cursor-pointer" 6998 } 6999 ) 7000 ] }),
7001 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 7002 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between text-[10px] text-zinc-500 mb-1", children: [ 7003 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Speed" }), 7004 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [ 7005 ((_V = currentConfigToEdit.particles) == null ? void 0 : _V.speed) || 1, 7006 "x" 7007 ] }) 7008 ] }), 7009 /* @__PURE__ */ jsxRuntimeExports.jsx( 7010 "input", 7011 { 7012 type: "range", 7013 min: "0.1", 7014 max: "5", 7015 step: "0.1", 7016 value: ((_W = currentConfigToEdit.particles) == null ? void 0 : _W.speed) || 1, 7017 onChange: (e) => { 7018 updateActiveConfig((prev) => ({ 7019 ...prev, 7020 particles: { ...prev.particles, speed: parseFloat(e.target.value) || 1 } 7021 })); 7022 }, 7023 className: "w-full accent-indigo-500 h-1.5 rounded-lg bg-zinc-800 cursor-pointer" 7024 } 7025 ) 7026 ] }),
7027 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 7028 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between text-[10px] text-zinc-500 mb-1", children: [ 7029 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Size" }), 7030 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [ 7031 ((_X = currentConfigToEdit.particles) == null ? void 0 : _X.size) || 2, 7032 "px" 7033 ] }) 7034 ] }), 7035 /* @__PURE__ */ jsxRuntimeExports.jsx( 7036 "input", 7037 { 7038 type: "range", 7039 min: "1", 7040 max: "10", 7041 step: "0.5", 7042 value: ((_Y = currentConfigToEdit.particles) == null ? void 0 : _Y.size) || 2, 7043 onChange: (e) => { 7044 updateActiveConfig((prev) => ({ 7045 ...prev, 7046 particles: { ...prev.particles, size: parseFloat(e.target.value) || 2 } 7047 })); 7048 }, 7049 className: "w-full accent-indigo-500 h-1.5 rounded-lg bg-zinc-800 cursor-pointer" 7050 } 7051 ) 7052 ] }) 7053 ] }) 7054 ] }) 7055 ] }),
7056 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pt-4 border-t border-zinc-800", children: /* @__PURE__ */ jsxRuntimeExports.jsxs( 7057 "button", 7058 { 7059 onClick: handleSave, 7060 className: "w-full py-2.5 bg-indigo-650 hover:bg-indigo-600 text-white text-sm font-bold rounded-lg transition-colors flex items-center justify-center gap-2 shadow-md shadow-indigo-650/10", 7061 children: [ 7062 /* @__PURE__ */ jsxRuntimeExports.jsx(Save, { size: 16 }), 7063 " Save Background Config" 7064 ] 7065 } 7066 ) }) 7067 ] }), 7068 !selectedConfig && activeTab === "characters" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-full flex items-center justify-center text-zinc-600", children: "Select a puppet configuration to edit" }) 7069 ] }), 7070 showRiggingBuilder && /* @__PURE__ */ jsxRuntimeExports.jsx( 7071 RiggingBuilder, 7072 { 7073 existingSvg: selectedConfig == null ? void 0 : selectedConfig.svgContent, 7074 onClose: () => setShowRiggingBuilder(false), 7075 onApplySvg: (svgContent) => { 7076 if (!selectedId) { 7077 const id = `new_puppet_${Date.now()}`; 7078 const defaultCfg = Object.values(defaultPuppetConfigs)[0]; 7079 setConfigs((prev) => ({ ...prev, [id]: { ...defaultCfg, id, name: "Rigged Character", svgContent } })); 7080 setSelectedId(id); 7081 } else { 7082 handleConfigChange(selectedId, { svgContent }); 7083 } 7084 setShowRiggingBuilder(false); 7085 } 7086 } 7087 ) 7088 ] }) 7089 ] }); 7090} 7091export { 7092 PuppetStudio as default 7093};
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.