PageSourceSearch

https://www.hamienet.com/assets/SouthparkCharacter-BNwA9Wd0.js

js hamienet.com collected 2026-09-24 19:01:22 UTC 223,424 bytes, 5,533 lines download raw bytes

1import { r as reactExports, R as React, j as jsxRuntimeExports } from "./vendor-react-Bay9Y7Lb.js";
2import { s as solve2BoneIK } from "./IkMath-D6fi1ppd.js";
3import { m as motion } from "./vendor-framer-C8eEgkdL.js";
4const BASE_ANIMALS = {
5  fox: {
6    id: "fox",
7    name: "Fox",
8    headUrl: "/assets/visualizers/southpark/animals/fox_head.webp",
9    bodyUrl: "/assets/visualizers/southpark/animals/fox_body.webp",
10    armLeftUrl: "/assets/visualizers/southpark/animals/fox_upper_arm_l.webp",
11    armRightUrl: "/assets/visualizers/southpark/animals/fox_upper_arm_r.webp",
12    mouthClosedUrl: "/assets/visualizers/southpark/animals/fox_mouth_closed.webp",
13    mouthOpenUrl: "/assets/visualizers/southpark/animals/fox_mouth_open.webp",
14    hasSeparateHead: true,
15    offsets: { headX: 0, headY: 0, scale: 1, armLeftX: 0, armLeftY: 0, armRightX: 0, armRightY: 0 },
16    furColor: "#eb6a24",
17    sleeveColor: "#eb6a24"
18  },
19  bear: {
20    id: "bear",
21    name: "Bear",
22    headUrl: "/assets/visualizers/southpark/animals/bear_head.webp",
23    bodyUrl: "/assets/visualizers/southpark/animals/bear_body.webp",
24    armLeftUrl: "/assets/visualizers/southpark/animals/bear_upper_arm_l.webp",
25    armRightUrl: "/assets/visualizers/southpark/animals/bear_upper_arm_r.webp",
26    mouthClosedUrl: "/assets/visualizers/southpark/animals/bear_mouth_closed.webp",
27    mouthOpenUrl: "/assets/visualizers/southpark/animals/bear_mouth_open.webp",
28    hasSeparateHead: true,
29    offsets: { headX: 0, headY: 0, scale: 1, armLeftX: 0, armLeftY: 0, armRightX: 0, armRightY: 0 },
30    furColor: "#8c5c3e",
31    sleeveColor: "#8c5c3e"
32  },
33  wolf: {
34    id: "wolf",
35    name: "Wolf",
36    headUrl: "/assets/visualizers/southpark/animals/wolf_head.webp",
37    bodyUrl: "/assets/visualizers/southpark/animals/wolf_body.webp",
38    armLeftUrl: "/assets/visualizers/southpark/animals/wolf_upper_arm_l.webp",
39    armRightUrl: "/assets/visualizers/southpark/animals/wolf_upper_arm_r.webp",
40    mouthClosedUrl: "/assets/visualizers/southpark/animals/wolf_mouth_closed.webp",
41    mouthOpenUrl: "/assets/visualizers/southpark/animals/wolf_mouth_open.webp",
42    hasSeparateHead: true,
43    offsets: { headX: 0, headY: 0, scale: 1, armLeftX: 0, armLeftY: 0, armRightX: 0, armRightY: 0 },
44    furColor: "#7d8692",
45    sleeveColor: "#7d8692"
46  },
47  lion: {
48    id: "lion",
49    name: "Lion",
50    headUrl: "/assets/visualizers/southpark/animals/lion_head.webp",
51    bodyUrl: "/assets/visualizers/southpark/animals/lion_body.webp",
52    armLeftUrl: "/assets/visualizers/southpark/animals/lion_upper_arm_l.webp",
53    armRightUrl: "/assets/visualizers/southpark/animals/lion_upper_arm_r.webp",
54    mouthClosedUrl: "/assets/visualizers/southpark/animals/lion_mouth_closed.webp",
55    mouthOpenUrl: "/assets/visualizers/southpark/animals/lion_mouth_open.webp",
56    hasSeparateHead: true,
57    offsets: { headX: 0, headY: 0, scale: 1, armLeftX: 0, armLeftY: 0, armRightX: 0, armRightY: 0 },
58    furColor: "#e09f3e",
59    sleeveColor: "#e09f3e"
60  },
61  owl: {
62    id: "owl",
63    name: "Owl",
64    headUrl: "/assets/visualizers/southpark/animals/owl_head.webp",
65    bodyUrl: "/assets/visualizers/southpark/animals/owl_body.webp",
66    armLeftUrl: "/assets/visualizers/southpark/animals/owl_upper_arm_l.webp",
67    armRightUrl: "/assets/visualizers/southpark/animals/owl_upper_arm_r.webp",
68    mouthClosedUrl: "/assets/visualizers/southpark/animals/owl_mouth_closed.webp",
69    mouthOpenUrl: "/assets/visualizers/southpark/animals/owl_mouth_open.webp",
70    hasSeparateHead: true,
71    offsets: { headX: 0, headY: 0, scale: 1, armLeftX: 0, armLeftY: 0, armRightX: 0, armRightY: 0 },
72    furColor: "#8b5a2b",
73    sleeveColor: "#8b5a2b"
74  },
75  raccoon: {
76    id: "raccoon",
77    name: "Raccoon",
78    headUrl: "/assets/visualizers/southpark/animals/raccoon_head.webp",
79    bodyUrl: "/assets/visualizers/southpark/animals/raccoon_body.webp",
80    armLeftUrl: "/assets/visualizers/southpark/animals/raccoon_upper_arm_l.webp",
81    armRightUrl: "/assets/visualizers/southpark/animals/raccoon_upper_arm_r.webp",
82    mouthClosedUrl: "/assets/visualizers/southpark/animals/raccoon_mouth_closed.webp",
83    mouthOpenUrl: "/assets/visualizers/southpark/animals/raccoon_mouth_open.webp",
84    hasSeparateHead: true,
85    offsets: { headX: 0, headY: 0, scale: 1, armLeftX: 0, armLeftY: 0, armRightX: 0, armRightY: 0 },
86    furColor: "#757575",
87    sleeveColor: "#757575"
88  },
89  hamster: {
90    id: "hamster",
91    name: "Hamster",
92    headUrl: "/assets/visualizers/southpark/animals/hamster_head.webp",
93    bodyUrl: "/assets/visualizers/southpark/animals/hamster_body.webp",
94    armLeftUrl: "/assets/visualizers/southpark/animals/hamster_upper_arm_l.webp",
95    armRightUrl: "/assets/visualizers/southpark/animals/hamster_upper_arm_r.webp",
96    mouthClosedUrl: "/assets/visualizers/southpark/animals/hamster_mouth_closed.webp",
97    mouthOpenUrl: "/assets/visualizers/southpark/animals/hamster_mouth_open.webp",
98    hasSeparateHead: true,
99    offsets: { headX: 0, headY: 0, scale: 1, armLeftX: 0, armLeftY: 0, armRightX: 0, armRightY: 0 },
100    furColor: "#d99d5a",
101    sleeveColor: "#d99d5a",
102    strokeColor: "#6c412c",
103    strokeWidth: 1.3
104  },
105  otter: {
106    id: "otter",
107    name: "Otter",
108    headUrl: "/assets/visualizers/southpark/animals/otter_head.webp",
109    bodyUrl: "/assets/visualizers/southpark/animals/otter_body.webp",
110    armLeftUrl: "/assets/visualizers/southpark/animals/otter_upper_arm_l.webp",
111    armRightUrl: "/assets/visualizers/southpark/animals/otter_upper_arm_r.webp",
112    mouthClosedUrl: "/assets/visualizers/southpark/animals/otter_mouth_closed.webp",
113    mouthOpenUrl: "/assets/visualizers/southpark/animals/otter_mouth_open.webp",
114    hasSeparateHead: true,
115    offsets: { headX: 0, headY: 0, scale: 1, armLeftX: 0, armLeftY: 0, armRightX: 0, armRightY: 0 },
116    furColor: "#ad7557",
117    sleeveColor: "#ad7557",
118    strokeColor: "#422617",
119    strokeWidth: 1.3
120  },
121  deer: {
122    id: "deer",
123    name: "Deer",
124    headUrl: "/assets/visualizers/southpark/animals/deer_head.webp",
125    bodyUrl: "/assets/visualizers/southpark/animals/deer_body.webp",
126    armLeftUrl: "/assets/visualizers/southpark/animals/deer_upper_arm_l.webp",
127    armRightUrl: "/assets/visualizers/southpark/animals/deer_upper_arm_r.webp",
128    mouthClosedUrl: "/assets/visualizers/southpark/animals/deer_mouth_closed.webp",
129    mouthOpenUrl: "/assets/visualizers/southpark/animals/deer_mouth_open.webp",
130    hasSeparateHead: true,
131    offsets: { headX: 0, headY: 0, scale: 1, armLeftX: 0, armLeftY: 0, armRightX: 0, armRightY: 0 },
132    furColor: "#ebbe6c",
133    sleeveColor: "#ebbe6c",
134    strokeColor: "#5c371e",
135    strokeWidth: 1.3
136  },
137  penguin: {
138    id: "penguin",
139    name: "Penguin",
140    headUrl: "/assets/visualizers/southpark/animals/penguin_head.webp",
141    bodyUrl: "/assets/visualizers/southpark/animals/penguin_body.webp",
142    armLeftUrl: "/assets/visualizers/southpark/animals/penguin_upper_arm_l.webp",
143    armRightUrl: "/assets/visualizers/southpark/animals/penguin_upper_arm_r.webp",
144    mouthClosedUrl: "/assets/visualizers/southpark/animals/penguin_mouth_closed.webp",
145    mouthOpenUrl: "/assets/visualizers/southpark/animals/penguin_mouth_open.webp",
146    hasSeparateHead: true,
147    offsets: { headX: 0, headY: 0, scale: 1, armLeftX: 0, armLeftY: 0, armRightX: 0, armRightY: 0 },
148    furColor: "#7d7c7e",
149    sleeveColor: "#7d7c7e",
150    strokeColor: "#1a202c",
151    strokeWidth: 1.3
152  },
153  badger: {
154    id: "badger",
155    name: "Badger",
156    headUrl: "/assets/visualizers/southpark/animals/badger_head.webp",
157    bodyUrl: "/assets/visualizers/southpark/animals/badger_body.webp",
158    armLeftUrl: "/assets/visualizers/southpark/animals/badger_upper_arm_l.webp",
159    armRightUrl: "/assets/visualizers/southpark/animals/badger_upper_arm_r.webp",
160    mouthClosedUrl: "/assets/visualizers/southpark/animals/badger_mouth_closed.webp",
161    mouthOpenUrl: "/assets/visualizers/southpark/animals/badger_mouth_open.webp",
162    hasSeparateHead: true,
163    offsets: { headX: 0, headY: 0, scale: 1, armLeftX: 0, armLeftY: 0, armRightX: 0, armRightY: 0 },
164    furColor: "#807f7e",
165    sleeveColor: "#807f7e",
166    strokeColor: "#242c38",
167    strokeWidth: 1.3
168  },
169  rabbit: {
170    id: "rabbit",
171    name: "Rabbit",
172    headUrl: "/assets/visualizers/southpark/animals/rabbit_head.webp",
173    bodyUrl: "/assets/visualizers/southpark/animals/rabbit_body.webp",
174    armLeftUrl: "/assets/visualizers/southpark/animals/rabbit_upper_arm_l.webp",
175    armRightUrl: "/assets/visualizers/southpark/animals/rabbit_upper_arm_r.webp",
176    mouthClosedUrl: "/assets/visualizers/southpark/animals/rabbit_mouth_closed.webp",
177    mouthOpenUrl: "/assets/visualizers/southpark/animals/rabbit_mouth_open.webp",
178    hasSeparateHead: true,
179    offsets: { headX: 0, headY: 0, scale: 1, armLeftX: 0, armLeftY: 0, armRightX: 0, armRightY: 0 },
180    furColor: "#d7a666",
181    sleeveColor: "#d7a666",
182    strokeColor: "#735e52",
183    strokeWidth: 1.3
184  },
185  koala: {
186    id: "koala",
187    name: "Koala",
188    headUrl: "/assets/visualizers/southpark/animals/koala_head.webp",
189    bodyUrl: "/assets/visualizers/southpark/animals/koala_body.webp",
190    armLeftUrl: "/assets/visualizers/southpark/animals/koala_upper_arm_l.webp",
191    armRightUrl: "/assets/visualizers/southpark/animals/koala_upper_arm_r.webp",
192    mouthClosedUrl: "/assets/visualizers/southpark/animals/koala_mouth_closed.webp",
193    mouthOpenUrl: "/assets/visualizers/southpark/animals/koala_mouth_open.webp",
194    hasSeparateHead: true,
195    offsets: { headX: 0, headY: 0, scale: 1, armLeftX: 0, armLeftY: 0, armRightX: 0, armRightY: 0 },
196    furColor: "#b4a696",
197    sleeveColor: "#b4a696",
198    strokeColor: "#474440",
199    strokeWidth: 1.3
200  },
201  bat: {
202    id: "bat",
203    name: "Bat",
204    headUrl: "/assets/visualizers/southpark/animals/bat_head.webp",
205    bodyUrl: "/assets/visualizers/southpark/animals/bat_body.webp",
206    armLeftUrl: "/assets/visualizers/southpark/animals/bat_upper_arm_l.webp",
207    armRightUrl: "/assets/visualizers/southpark/animals/bat_upper_arm_r.webp",
208    mouthClosedUrl: "/assets/visualizers/southpark/animals/bat_mouth_closed.webp",
209    mouthOpenUrl: "/assets/visualizers/southpark/animals/bat_mouth_open.webp",
210    hasSeparateHead: true,
211    offsets: { headX: 0, headY: 0, scale: 1, armLeftX: 0, armLeftY: 0, armRightX: 0, armRightY: 0 },
212    furColor: "#836174",
213    sleeveColor: "#836174",
214    strokeColor: "#2e1b36",
215    strokeWidth: 1.3
216  },
217  llama: {
218    id: "llama",
219    name: "Llama",
220    headUrl: "/assets/visualizers/southpark/animals/llama_head.webp",
221    bodyUrl: "/assets/visualizers/southpark/animals/llama_body.webp",
222    armLeftUrl: "/assets/visualizers/southpark/animals/llama_upper_arm_l.webp",
223    armRightUrl: "/assets/visualizers/southpark/animals/llama_upper_arm_r.webp",
224    mouthClosedUrl: "/assets/visualizers/southpark/animals/llama_mouth_closed.webp",
225    mouthOpenUrl: "/assets/visualizers/southpark/animals/llama_mouth_open.webp",
226    hasSeparateHead: true,
227    offsets: { headX: 0, headY: 0, scale: 1, armLeftX: 0, armLeftY: 0, armRightX: 0, armRightY: 0 },
228    furColor: "#ebe3d1",
229    sleeveColor: "#ebe3d1",
230    strokeColor: "#75644d",
231    strokeWidth: 1.3
232  },
233  gorilla: {
234    id: "gorilla",
235    name: "Gorilla",
236    headUrl: "/assets/visualizers/southpark/animals/gorilla_head.webp",
237    bodyUrl: "/assets/visualizers/southpark/animals/gorilla_body.webp",
238    armLeftUrl: "/assets/visualizers/southpark/animals/gorilla_upper_arm_l.webp",
239    armRightUrl: "/assets/visualizers/southpark/animals/gorilla_upper_arm_r.webp",
240    mouthClosedUrl: "/assets/visualizers/southpark/animals/gorilla_mouth_closed.webp",
241    mouthOpenUrl: "/assets/visualizers/southpark/animals/gorilla_mouth_open.webp",
242    hasSeparateHead: true,
243    offsets: { headX: 0, headY: 0, scale: 1, armLeftX: 0, armLeftY: 0, armRightX: 0, armRightY: 0 },
244    furColor: "#606369",
245    sleeveColor: "#606369",
246    strokeColor: "#1c1d22",
247    strokeWidth: 1.3
248  },
249  parrot: {
250    id: "parrot",
251    name: "Parrot",
252    headUrl: "/assets/visualizers/southpark/animals/parrot_head.webp",
253    bodyUrl: "/assets/visualizers/southpark/animals/parrot_body.webp",
254    armLeftUrl: "/assets/visualizers/southpark/animals/parrot_upper_arm_l.webp",
255    armRightUrl: "/assets/visualizers/southpark/animals/parrot_upper_arm_r.webp",
256    mouthClosedUrl: "/assets/visualizers/southpark/animals/parrot_mouth_closed.webp",
257    mouthOpenUrl: "/assets/visualizers/southpark/animals/parrot_mouth_open.webp",
258    hasSeparateHead: true,
259    offsets: { headX: 0, headY: 0, scale: 1, armLeftX: 0, armLeftY: 0, armRightX: 0, armRightY: 0 },
260    furColor: "#b63e43",
261    sleeveColor: "#b63e43",
262    strokeColor: "#68181a",
263    strokeWidth: 1.3
264  }
265};
266const SOUTHPARK_ANIMALS = {
267  // 0: Piano -> Hamster
268  0: { ...BASE_ANIMALS.hamster, name: "Piano Hamster" },
269  // 1: Chromatic Percussion -> Otter
270  1: { ...BASE_ANIMALS.otter, name: "Mallet Otter" },
271  // 2: Organ -> Bear
272  2: { ...BASE_ANIMALS.bear, name: "Organ Bear" },
273  // 3: Guitar -> Fox
274  3: { ...BASE_ANIMALS.fox, name: "Guitar Fox" },
275  // 4: Bass -> Wolf
276  4: { ...BASE_ANIMALS.wolf, name: "Bass Wolf" },
277  // 5: Strings -> Deer
278  5: { ...BASE_ANIMALS.deer, name: "Strings Deer" },
279  // 6: Ensemble -> Penguin
280  6: { ...BASE_ANIMALS.penguin, name: "Ensemble Penguin" },
281  // 7: Brass -> Lion
282  7: { ...BASE_ANIMALS.lion, name: "Brass Lion" },
283  // 8: Reed -> Badger
284  8: { ...BASE_ANIMALS.badger, name: "Reed Badger" },
285  // 9: Pipe -> Owl
286  9: { ...BASE_ANIMALS.owl, name: "Pipe Owl" },
287  // 10: Synth Lead -> Rabbit
288  10: { ...BASE_ANIMALS.rabbit, name: "Lead Rabbit" },
289  // 11: Synth Pad -> Koala
290  11: { ...BASE_ANIMALS.koala, name: "Pad Koala" },
291  // 12: Synth FX -> Bat
292  12: { ...BASE_ANIMALS.bat, name: "FX Bat" },
293  // 13: Ethnic -> Llama
294  13: { ...BASE_ANIMALS.llama, name: "Ethnic Llama" },
295  // 14: Percussive -> Gorilla
296  14: { ...BASE_ANIMALS.gorilla, name: "Percussion Gorilla" },
297  // 15: Sound Effects -> Parrot
298  15: { ...BASE_ANIMALS.parrot, name: "SFX Parrot" },
299  // -1: Percussion / Drums (Ch 9) -> Raccoon
300  "-1": { ...BASE_ANIMALS.raccoon, name: "Drummer Raccoon" }
301};
302function getAnimalForTrack(program, isPercussion, globalConfig, percRole) {
303  if (globalConfig == null ? void 0 : globalConfig.instruments) {
304    const mappingId = isPercussion ? 128 : program;
305    const mapping = globalConfig.instruments[mappingId];
306    if (mapping && mapping.animalId && BASE_ANIMALS[mapping.animalId]) {
307      return { ...BASE_ANIMALS[mapping.animalId], name: `Custom ${BASE_ANIMALS[mapping.animalId].name}` };
308    }
309  }
310  if (isPercussion) {
311    if (percRole === "tambourine") return { ...BASE_ANIMALS.otter, name: "Tambourine Otter" };
312    if (percRole === "cowbell") return { ...BASE_ANIMALS.badger, name: "Cowbell Badger" };
313    if (percRole === "congas") return { ...BASE_ANIMALS.gorilla, name: "Conga Gorilla" };
314    if (percRole === "bongos") return { ...BASE_ANIMALS.llama, name: "Bongo Llama" };
315    if (percRole === "triangle") return { ...BASE_ANIMALS.hamster, name: "Triangle Hamster" };
316    if (percRole === "shaker") return { ...BASE_ANIMALS.parrot, name: "Shaker Parrot" };
317    if (percRole === "claps") return { ...BASE_ANIMALS.rabbit, name: "Clapping Rabbit" };
318    return SOUTHPARK_ANIMALS[-1] || BASE_ANIMALS.raccoon;
319  }
320  const family = Math.floor(Math.max(0, Math.min(127, program)) / 8);
321  return SOUTHPARK_ANIMALS[family] || BASE_ANIMALS.fox;
322}
323function getAnimalForProgram(program, isPercussion = false, globalConfig, percRole) {
324  return getAnimalForTrack(program, isPercussion, globalConfig, percRole);
325}
326const SOUTHPARK_INSTRUMENTS = {};
327const GM_INSTRUMENT_NAMES = [
328  "Acoustic Grand Piano",
329  "Bright Acoustic Piano",
330  "Electric Grand Piano",
331  "Honky-tonk Piano",
332  "Electric Piano 1",
333  "Electric Piano 2",
334  "Harpsichord",
335  "Clavi",
336  "Celesta",
337  "Glockenspiel",
338  "Music Box",
339  "Vibraphone",
340  "Marimba",
341  "Xylophone",
342  "Tubular Bells",
343  "Dulcimer",
344  "Drawbar Organ",
345  "Percussive Organ",
346  "Rock Organ",
347  "Church Organ",
348  "Reed Organ",
349  "Accordion",
350  "Harmonica",
351  "Tango Accordion",
352  "Acoustic Guitar (nylon)",
353  "Acoustic Guitar (steel)",
354  "Electric Guitar (jazz)",
355  "Electric Guitar (clean)",
356  "Electric Guitar (muted)",
357  "Overdriven Guitar",
358  "Distortion Guitar",
359  "Guitar harmonics",
360  "Acoustic Bass",
361  "Electric Bass (finger)",
362  "Electric Bass (pick)",
363  "Fretless Bass",
364  "Slap Bass 1",
365  "Slap Bass 2",
366  "Synth Bass 1",
367  "Synth Bass 2",
368  "Violin",
369  "Viola",
370  "Cello",
371  "Contrabass",
372  "Tremolo Strings",
373  "Pizzicato Strings",
374  "Orchestral Harp",
375  "Timpani",
376  "String Ensemble 1",
377  "String Ensemble 2",
378  "SynthStrings 1",
379  "SynthStrings 2",
380  "Choir Aahs",
381  "Voice Oohs",
382  "Synth Voice",
383  "Orchestra Hit",
384  "Trumpet",
385  "Trombone",
386  "Tuba",
387  "Muted Trumpet",
388  "French Horn",
389  "Brass Section",
390  "SynthBrass 1",
391  "SynthBrass 2",
392  "Soprano Sax",
393  "Alto Sax",
394  "Tenor Sax",
395  "Baritone Sax",
396  "Oboe",
397  "English Horn",
398  "Bassoon",
399  "Clarinet",
400  "Piccolo",
401  "Flute",
402  "Recorder",
403  "Pan Flute",
404  "Blown Bottle",
405  "Shakuhachi",
406  "Whistle",
407  "Ocarina",
408  "Lead 1 (square)",
409  "Lead 2 (sawtooth)",
410  "Lead 3 (calliope)",
411  "Lead 4 (chiff)",
412  "Lead 5 (charang)",
413  "Lead 6 (voice)",
414  "Lead 7 (fifths)",
415  "Lead 8 (bass + lead)",
416  "Pad 1 (new age)",
417  "Pad 2 (warm)",
418  "Pad 3 (polysynth)",
419  "Pad 4 (choir)",
420  "Pad 5 (bowed)",
421  "Pad 6 (metallic)",
422  "Pad 7 (halo)",
423  "Pad 8 (sweep)",
424  "FX 1 (rain)",
425  "FX 2 (soundtrack)",
426  "FX 3 (crystal)",
427  "FX 4 (atmosphere)",
428  "FX 5 (brightness)",
429  "FX 6 (goblins)",
430  "FX 7 (echoes)",
431  "FX 8 (sci-fi)",
432  "Sitar",
433  "Banjo",
434  "Shamisen",
435  "Koto",
436  "Kalimba",
437  "Bag pipe",
438  "Fiddle",
439  "Shanai",
440  "Tinkle Bell",
441  "Agogo",
442  "Steel Drums",
443  "Woodblock",
444  "Taiko Drum",
445  "Melodic Tom",
446  "Synth Drum",
447  "Reverse Cymbal",
448  "Guitar Fret Noise",
449  "Breath Noise",
450  "Seashore",
451  "Bird Tweet",
452  "Telephone Ring",
453  "Helicopter",
454  "Applause",
455  "Gunshot"
456];
457function getDefaultJointDef(program, holdStyle) {
458  if (holdStyle === "violin") {
459    return {
460      jointType: "bowed_strings",
461      leftRole: "bow_frog",
462      rightRole: "neck_fret",
463      contactPoint: { x: 4, y: 16 },
464      neckVector: { startX: 10, startY: 14, endX: 24, endY: 2 }
465    };
466  }
467  if (holdStyle === "bowing") {
468    return {
469      jointType: "bowed_strings",
470      leftRole: "bow_frog",
471      rightRole: "neck_fret",
472      contactPoint: { x: 0, y: 34 },
473      neckVector: { startX: 0, startY: 22, endX: 10, endY: -8 }
474    };
475  }
476  if (holdStyle === "guitar") {
477    return {
478      jointType: "guitar_plucked",
479      leftRole: "strum",
480      rightRole: "neck_fret",
481      neckVector: { startX: 8, startY: 28, endX: 28, endY: 8 }
482    };
483  }
484  if (program === 57) {
485    return {
486      jointType: "slide_brass",
487      leftRole: "valve_brace",
488      rightRole: "slide_grip",
489      slideVector: { angle: 18, minExtension: 0, maxExtension: 24 }
490    };
491  }
492  if (holdStyle === "flute") {
493    return {
494      jointType: "flute_transverse",
495      leftRole: "flute_near",
496      rightRole: "flute_far"
497    };
498  }
499  if (holdStyle === "blowing") {
500    if (program === 56 || program === 58 || program === 59 || program === 60 || program === 61) {
501      return {
502        jointType: "valved_brass",
503        leftRole: "valve_brace",
504        rightRole: "valve_fingers"
505      };
506    }
507    return {
508      jointType: "woodwind_mouth",
509      leftRole: "reed_upper",
510      rightRole: "reed_lower"
511    };
512  }
513  if (holdStyle === "drumming") {
514    if (program === 47) {
515      return {
516        jointType: "mallet_bars",
517        leftRole: "mallet",
518        rightRole: "mallet"
519      };
520    }
521    return {
522      jointType: "drum_sticks",
523      leftRole: "drumstick",
524      rightRole: "drumstick"
525    };
526  }
527  if (holdStyle === "sitting") {
528    if (program >= 8 && program <= 15 && program !== 8 && program !== 10 || program === 114) {
529      return {
530        jointType: "mallet_bars",
531        leftRole: "mallet",
532        rightRole: "mallet"
533      };
534    }
535    return {
536      jointType: "seated_keys",
537      leftRole: "keys",
538      rightRole: "keys"
539    };
540  }
541  if (holdStyle === "congas") {
542    return {
543      jointType: "hand_drums",
544      leftRole: "conga_slap",
545      rightRole: "conga_slap"
546    };
547  }
548  if (holdStyle === "percussion_handheld") {
549    if (program === -7 || program === -4 || program === 112 || program === 113 || program === 115) {
550      return {
551        jointType: "handheld_striker",
552        leftRole: "hold_body",
553        rightRole: "striker_stick"
554      };
555    }
556    return {
557      jointType: "hand_shaker",
558      leftRole: "hold_body",
559      rightRole: "shake"
560    };
561  }
562  if (program === 21 || program === 23 || holdStyle === "accordion") {
563    return {
564      jointType: "accordion_bellows",
565      leftRole: "bellows_bass",
566      rightRole: "bellows_treble"
567    };
568  }
569  if (program === 22 || holdStyle === "harmonica") {
570    return {
571      jointType: "woodwind_mouth",
572      leftRole: "reed_upper",
573      rightRole: "reed_lower"
574    };
575  }
576  if (program === 46 || program === 107 || holdStyle === "harp") {
577    return {
578      jointType: "harp_plucking",
579      leftRole: "harp_pluck",
580      rightRole: "harp_pluck"
581    };
582  }
583  if (holdStyle === "pizzicato") {
584    return {
585      jointType: "guitar_plucked",
586      leftRole: "strum",
587      rightRole: "neck_fret"
588    };
589  }
590  return {
591    jointType: "bare_hands",
592    leftRole: "idle",
593    rightRole: "idle"
594  };
595}
596const INSTRUMENT_CONFIGS = {
597  // 1. Pianos & Keyboards (0 - 7)
598  0: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
599  1: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
600  2: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
601  3: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
602  4: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
603  5: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
604  6: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
605  7: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
606  // 2. Chromatic Percussion (8 - 15)
607  8: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
608  9: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
609  10: { holdStyle: "percussion_handheld", scale: 1, offsetX: 0, offsetY: 0 },
610  // Music Box (handheld prop)
611  11: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
612  12: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
613  13: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
614  14: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
615  // Tubular Bells (grounded concert chimes)
616  15: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
617  // 3. Organs & Free Reeds (16 - 23)
618  16: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
619  17: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
620  18: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
621  19: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
622  // Church Organ
623  20: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
624  21: { holdStyle: "accordion", scale: 1, offsetX: 0, offsetY: 0 },
625  // Accordion (chest-strapped)
626  22: { holdStyle: "harmonica", scale: 0.85, offsetX: 0, offsetY: 0 },
627  // Harmonica (mouth held)
628  23: { holdStyle: "accordion", scale: 1, offsetX: 0, offsetY: 0 },
629  // Tango Accordion
630  // 4. Guitars (24 - 31)
631  24: { holdStyle: "guitar", scale: 1, offsetX: 0, offsetY: 0 },
632  25: { holdStyle: "guitar", scale: 1, offsetX: 0, offsetY: 0 },
633  26: { holdStyle: "guitar", scale: 1, offsetX: 0, offsetY: 0 },
634  27: { holdStyle: "guitar", scale: 1, offsetX: 0, offsetY: 0 },
635  28: { holdStyle: "guitar", scale: 1, offsetX: 0, offsetY: 0 },
636  29: { holdStyle: "guitar", scale: 1, offsetX: 0, offsetY: 0 },
637  30: { holdStyle: "guitar", scale: 1, offsetX: 0, offsetY: 0 },
638  31: { holdStyle: "guitar", scale: 1, offsetX: 0, offsetY: 0 },
639  // 5. Basses (32 - 39)
640  32: { holdStyle: "bowing", scale: 1, offsetX: 0, offsetY: 0 },
641  // Upright Double Bass (grounded floor)
642  33: { holdStyle: "guitar", scale: 1, offsetX: 0, offsetY: 0 },
643  34: { holdStyle: "guitar", scale: 1, offsetX: 0, offsetY: 0 },
644  35: { holdStyle: "guitar", scale: 1, offsetX: 0, offsetY: 0 },
645  36: { holdStyle: "guitar", scale: 1, offsetX: 0, offsetY: 0 },
646  37: { holdStyle: "guitar", scale: 1, offsetX: 0, offsetY: 0 },
647  38: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
648  // Minimoog Synth Bass
649  39: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
650  // TB-303 Synth Bass
651  // 6. Bowed Strings & Timpani (40 - 47)
652  40: { holdStyle: "violin", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
653  // Violin (under chin / shoulder)
654  41: { holdStyle: "violin", scale: 1.08, offsetX: 0, offsetY: 0, rotation: 0 },
655  // Viola (larger body)
656  42: { holdStyle: "bowing", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
657  // Cello (seated grounded)
658  43: { holdStyle: "bowing", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
659  // Contrabass (3/4 turned acoustic)
660  44: { holdStyle: "violin", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
661  // Tremolo Strings
662  45: { holdStyle: "pizzicato", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
663  // Pizzicato Strings
664  46: { holdStyle: "harp", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
665  // Concert Harp (grounded 3/4 stance)
666  47: { holdStyle: "drumming", scale: 1.05, offsetX: 0, offsetY: 0, rotation: 0 },
667  // Timpani (grounded)
668  // 7. Ensemble & Choral (48 - 55)
669  48: { holdStyle: "violin", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
670  // String Ensemble 1
671  49: { holdStyle: "violin", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
672  // String Ensemble 2
673  50: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
674  // SynthStrings 1 (Solina on Z-Stand)
675  51: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
676  // SynthStrings 2 (Roland on X-Stand)
677  52: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
678  // Choir Aahs (Choral Binder & Stand)
679  53: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
680  // Voice Oohs (Vintage Shure 55SH Mic)
681  54: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
682  // Synth Voice (Vocoder with Gooseneck)
683  55: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
684  // Orchestra Hit (Conductor Stand & Score)
685  // 8. Brass (56 - 63)
686  56: { holdStyle: "blowing", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
687  // Trumpet
688  57: { holdStyle: "blowing", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
689  // Trombone
690  58: { holdStyle: "blowing", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
691  // Tuba
692  59: { holdStyle: "blowing", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
693  // Muted Trumpet
694  60: { holdStyle: "blowing", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
695  // French Horn
696  61: { holdStyle: "blowing", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
697  62: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
698  // Synth Brass 1
699  63: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
700  // Synth Brass 2
701  // 9. Woodwinds (Reeds) (64 - 71)
702  64: { holdStyle: "blowing", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
703  // Soprano Sax
704  65: { holdStyle: "blowing", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
705  // Alto Sax
706  66: { holdStyle: "blowing", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
707  // Tenor Sax
708  67: { holdStyle: "blowing", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
709  // Baritone Sax
710  68: { holdStyle: "blowing", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
711  // Oboe
712  69: { holdStyle: "blowing", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
713  // English Horn
714  70: { holdStyle: "blowing", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
715  // Bassoon
716  71: { holdStyle: "blowing", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
717  // Clarinet
718  // 10. Woodwinds (Pipes & Flutes) (72 - 79)
719  72: { holdStyle: "flute", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
720  // Piccolo (transverse side)
721  73: { holdStyle: "flute", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
722  // Concert Flute (transverse side)
723  74: { holdStyle: "blowing", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
724  // Recorder
725  75: { holdStyle: "blowing", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
726  // Pan Flute
727  76: { holdStyle: "blowing", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
728  // Blown Bottle
729  77: { holdStyle: "blowing", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
730  // Shakuhachi
731  78: { holdStyle: "blowing", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
732  // Whistle
733  79: { holdStyle: "blowing", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
734  // Ocarina
735  // 11. Synth Leads (80 - 87)
736  80: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
737  // Lead 1 (Square - Moog on X-Stand)
738  81: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
739  // Lead 2 (Sawtooth - Prophet-5 on X-Stand)
740  82: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
741  // Lead 3 (Calliope - Fairground Whistles on X-Stand)
742  83: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
743  // Lead 4 (Chiff - DX7 FM Synth on X-Stand)
744  84: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
745  // Lead 5 (Charang - Overdrive Lead on X-Stand)
746  85: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
747  // Lead 6 (Voice - Vocoder VP-330 with Mic)
748  86: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
749  // Lead 7 (Fifths - Modular Patch Synth)
750  87: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
751  // Lead 8 (Bass + Lead - Two-Tier Workstation)
752  // 12. Synth Pads (88 - 95)
753  88: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
754  89: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
755  90: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
756  91: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
757  92: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
758  93: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
759  94: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
760  95: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
761  // 13. Synth Effects (96 - 103)
762  96: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
763  97: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
764  98: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
765  99: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
766  100: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
767  101: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
768  102: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
769  103: { holdStyle: "sitting", scale: 1, offsetX: 0, offsetY: 0 },
770  // 14. Ethnic & World (104 - 111)
771  104: { holdStyle: "guitar", scale: 1.15, offsetX: 0, offsetY: 0 },
772  // Sitar
773  105: { holdStyle: "guitar", scale: 1, offsetX: 0, offsetY: 0 },
774  // Banjo
775  106: { holdStyle: "guitar", scale: 1, offsetX: 0, offsetY: 0 },
776  // Shamisen
777  107: { holdStyle: "sitting", scale: 1.15, offsetX: 0, offsetY: 0 },
778  // Koto
779  108: { holdStyle: "percussion_handheld", scale: 1, offsetX: 0, offsetY: 0 },
780  // Kalimba
781  109: { holdStyle: "blowing", scale: 1.1, offsetX: 0, offsetY: 0 },
782  // Bagpipe
783  110: { holdStyle: "violin", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 },
784  // Fiddle
785  111: { holdStyle: "blowing", scale: 1, offsetX: 0, offsetY: 0 },
786  // Shanai
787  // 15. Percussive (Melodic) (112 - 119)
788  112: { holdStyle: "percussion_handheld", scale: 1, offsetX: 0, offsetY: 0 },
789  // Tinkle Bell
790  113: { holdStyle: "percussion_handheld", scale: 1, offsetX: 0, offsetY: 0 },
791  // Agogo
792  114: { holdStyle: "drumming", scale: 1, offsetX: 0, offsetY: 0 },
793  // Steel Drums
794  115: { holdStyle: "percussion_handheld", scale: 1, offsetX: 0, offsetY: 0 },
795  // Woodblock
796  116: { holdStyle: "drumming", scale: 1, offsetX: 0, offsetY: 0 },
797  // Taiko Drum
798  117: { holdStyle: "drumming", scale: 1, offsetX: 0, offsetY: 0 },
799  // Melodic Tom
800  118: { holdStyle: "drumming", scale: 1, offsetX: 0, offsetY: 0 },
801  // Synth Drum
802  119: { holdStyle: "drumming", scale: 1, offsetX: 0, offsetY: 0 },
803  // Reverse Cymbal
804  // 16. Sound Effects & Foley (120 - 127)
805  120: { holdStyle: "guitar", scale: 1, offsetX: 0, offsetY: 0 },
806  121: { holdStyle: "blowing", scale: 1, offsetX: 0, offsetY: 0 },
807  122: { holdStyle: "percussion_handheld", scale: 1, offsetX: 0, offsetY: 0 },
808  // Seashore (Ocean Drum)
809  123: { holdStyle: "blowing", scale: 1, offsetX: 0, offsetY: 0 },
810  // Bird Tweet (Water Warbler)
811  124: { holdStyle: "percussion_handheld", scale: 1, offsetX: 0, offsetY: 0 },
812  // Telephone Ring
813  125: { holdStyle: "percussion_handheld", scale: 1, offsetX: 0, offsetY: 0 },
814  // Helicopter
815  126: { holdStyle: "percussion_handheld", scale: 1, offsetX: 0, offsetY: 0 },
816  // Applause
817  127: { holdStyle: "percussion_handheld", scale: 1, offsetX: 0, offsetY: 0 }
818  // Gunshot
819};
820const SOUTHPARK_GUITAR_SVG = `<g id="guitar-inst" transform="translate(6, 24) rotate(22) scale(0.52)"><ellipse cx="2" cy="100" rx="34" ry="42" fill="#b26d3b" stroke="#0f0f0f" stroke-width="4.5"/><ellipse cx="2" cy="36" rx="26" ry="32" fill="#b26d3b" stroke="#0f0f0f" stroke-width="4.5"/><rect x="-22" y="46" width="48" height="24" fill="#b26d3b"/><circle cx="2" cy="54" r="13" fill="#18181b" stroke="#0f0f0f" stroke-width="3"/><rect x="-4.5" y="-45" width="13" height="98" fill="#5c361e" stroke="#0f0f0f" stroke-width="3"/><polygon points="-8,-45 12,-45 9,-65 -5,-65" fill="#3a1e0a" stroke="#0f0f0f" stroke-width="2.5"/><rect x="2" y="132" width="26" height="8" rx="2" fill="#3a1e0a" stroke="#0f0f0f" stroke-width="2"/><line x1="-2" y1="-45" x2="-2" y2="132" stroke="#ffffff" stroke-width="1.2"/><line x1="2" y1="-45" x2="2" y2="132" stroke="#ffffff" stroke-width="1.2"/><line x1="6" y1="-45" x2="6" y2="132" stroke="#ffffff" stroke-width="1.2"/></g>`;
821const SOUTHPARK_CELLO_SVG = `<g id="cello-inst" transform="translate(0, 24) scale(0.64)"><path d="M 0 20 C -12 20 -20 30 -20 44 C -20 54 -14 60 -8 63 C -16 68 -22 80 -22 96 C -22 115 -8 130 10 130 C 28 130 42 115 42 96 C 42 80 36 68 28 63 C 34 60 40 54 40 44 C 40 30 32 20 20 20 Z" fill="#b0521e" stroke="#0f0f0f" stroke-width="3.5"/><rect x="7" y="-28" width="6" height="48" fill="#2c1a0e" stroke="#0f0f0f" stroke-width="2.5"/><circle cx="10" cy="-31" r="5" fill="#2c1a0e" stroke="#0f0f0f" stroke-width="2"/><polygon points="10,80 7,108 13,108" fill="#0f0f0f"/><path d="M -3 55 C -5 65 0 75 -2 82" stroke="#0f0f0f" stroke-width="2" fill="none"/><path d="M 23 55 C 25 65 20 75 22 82" stroke="#0f0f0f" stroke-width="2" fill="none"/><line x1="8" y1="-28" x2="8" y2="80" stroke="#f1f5f9" stroke-width="1.2"/><line x1="12" y1="-28" x2="12" y2="80" stroke="#f1f5f9" stroke-width="1.2"/></g>`;
822const SOUTHPARK_DRUMS_SVG = `<g id="drums-inst" transform="translate(0, 58) scale(0.66)"><ellipse cx="-45" cy="0" rx="28" ry="14" fill="#e2e8f0" stroke="#0f0f0f" stroke-width="3.5"/><ellipse cx="45" cy="0" rx="28" ry="14" fill="#e2e8f0" stroke="#0f0f0f" stroke-width="3.5"/><ellipse cx="0" cy="18" rx="34" ry="16" fill="#f8fafc" stroke="#0f0f0f" stroke-width="3.5"/><circle cx="0" cy="35" r="38" fill="#1e293b" stroke="#0f0f0f" stroke-width="4"/><ellipse cx="-75" cy="-25" rx="32" ry="7" fill="#fbbf24" stroke="#0f0f0f" stroke-width="3"/><ellipse cx="75" cy="-25" rx="32" ry="7" fill="#fbbf24" stroke="#0f0f0f" stroke-width="3"/><line x1="-75" y1="-25" x2="-75" y2="60" stroke="#475569" stroke-width="4"/><line x1="75" y1="-25" x2="75" y2="60" stroke="#475569" stroke-width="4"/></g>`;
823const SOUTHPARK_PIANO_SVG = `<g id="piano-inst" transform="translate(0, 48) scale(0.66)"><rect x="-85" y="-12" width="170" height="24" rx="4" fill="#18181b" stroke="#000" stroke-width="3.5"/><rect x="-40" y="-6" width="80" height="12" rx="2" fill="#d97706" stroke="#09090b" stroke-width="1"/><polygon points="-28,-12 28,-12 32,-38 -32,-38" fill="#18181b" stroke="#09090b" stroke-width="2.5"/><polygon points="-24,-15 24,-15 27,-35 -27,-35" fill="#fafafa"/><line x1="-70" y1="12" x2="-70" y2="106" stroke="#18181b" stroke-width="7" stroke-linecap="round"/><circle cx="-70" cy="106" r="4" fill="#f59e0b"/><line x1="70" y1="12" x2="70" y2="106" stroke="#18181b" stroke-width="7" stroke-linecap="round"/><circle cx="70" cy="106" r="4" fill="#f59e0b"/><rect x="-14" y="96" width="28" height="10" rx="2" fill="#18181b"/><line x1="-6" y1="12" x2="-6" y2="96" stroke="#f59e0b" stroke-width="2"/><line x1="6" y1="12" x2="6" y2="96" stroke="#f59e0b" stroke-width="2"/></g>`;
824const SOUTHPARK_TRUMPET_SVG = `<g id="trumpet-inst" transform="translate(-4, 18) rotate(-6) scale(0.68)"><rect x="-8" y="-3" width="8" height="6" fill="#f59e0b" stroke="#0f0f0f" stroke-width="2" rx="1"/><line x1="-8" y1="0" x2="4" y2="0" stroke="#f59e0b" stroke-width="4"/><rect x="0" y="-3.5" width="48" height="7" fill="#f59e0b" stroke="#0f0f0f" stroke-width="2.5"/><rect x="14" y="-14" width="18" height="22" fill="#d97706" stroke="#0f0f0f" stroke-width="2.5" rx="2"/><rect x="16" y="-19" width="3.5" height="7" fill="#fbbf24" stroke="#0f0f0f" stroke-width="1.5"/><rect x="21" y="-19" width="3.5" height="7" fill="#fbbf24" stroke="#0f0f0f" stroke-width="1.5"/><rect x="26" y="-19" width="3.5" height="7" fill="#fbbf24" stroke="#0f0f0f" stroke-width="1.5"/><circle cx="17.7" cy="-19" r="2.5" fill="#fef08a" stroke="#0f0f0f" stroke-width="1"/><circle cx="22.7" cy="-19" r="2.5" fill="#fef08a" stroke="#0f0f0f" stroke-width="1"/><circle cx="27.7" cy="-19" r="2.5" fill="#fef08a" stroke="#0f0f0f" stroke-width="1"/><polygon points="46,-4 76,-18 76,12 46,2" fill="#f59e0b" stroke="#0f0f0f" stroke-width="2.5"/><ellipse cx="76" cy="-3" rx="4.5" ry="15" fill="#d97706" stroke="#0f0f0f" stroke-width="2.5"/></g>`;
825for (let i = 0; i < 128; i++) {
826  const cfg = INSTRUMENT_CONFIGS[i] || { holdStyle: "standard", scale: 1, offsetX: 0, offsetY: 0, rotation: 0 };
827  const joint = cfg.jointDef || getDefaultJointDef(i, cfg.holdStyle);
828  SOUTHPARK_INSTRUMENTS[i] = {
829    program: i,
830    name: GM_INSTRUMENT_NAMES[i],
831    svgContent: void 0,
832    imgUrl: `/assets/visualizers/southpark/instruments/inst_${i}.webp`,
833    holdStyle: cfg.holdStyle,
834    scale: cfg.scale,
835    offsetX: cfg.offsetX,
836    offsetY: cfg.offsetY,
837    rotation: cfg.rotation || 0,
838    jointDef: joint
839  };
840}
841const PERCUSSION_INSTRUMENTS = {
842  drums: {
843    program: -1,
844    name: "Standard Drum Kit",
845    imgUrl: "/assets/visualizers/southpark/instruments/inst_drums.webp",
846    holdStyle: "drumming",
847    scale: 1,
848    offsetX: 0,
849    offsetY: 0,
850    rotation: 0,
851    jointDef: getDefaultJointDef(-1, "drumming")
852  },
853  electronic: {
854    program: -2,
855    name: "Electronic Beat Machine",
856    imgUrl: "/assets/visualizers/southpark/instruments/perc_electronic.webp",
857    holdStyle: "sitting",
858    scale: 1,
859    offsetX: 0,
860    offsetY: 0,
861    rotation: 0,
862    jointDef: getDefaultJointDef(-2, "sitting")
863  },
864  tambourine: {
865    program: -3,
866    name: "Tambourine",
867    imgUrl: "/assets/visualizers/southpark/instruments/perc_tambourine.webp",
868    holdStyle: "percussion_handheld",
869    scale: 1,
870    offsetX: 0,
871    offsetY: 0,
872    rotation: 0,
873    jointDef: getDefaultJointDef(-3, "percussion_handheld")
874  },
875  cowbell: {
876    program: -4,
877    name: "Cowbell",
878    imgUrl: "/assets/visualizers/southpark/instruments/perc_cowbell.webp",
879    holdStyle: "percussion_handheld",
880    scale: 1,
881    offsetX: 0,
882    offsetY: 0,
883    rotation: 0,
884    jointDef: getDefaultJointDef(-4, "percussion_handheld")
885  },
886  congas: {
887    program: -5,
888    name: "Congas",
889    imgUrl: "/assets/visualizers/southpark/instruments/perc_congas.webp",
890    holdStyle: "congas",
891    scale: 1,
892    offsetX: 0,
893    offsetY: 0,
894    rotation: 0,
895    jointDef: getDefaultJointDef(-5, "congas")
896  },
897  bongos: {
898    program: -6,
899    name: "Bongos",
900    imgUrl: "/assets/visualizers/southpark/instruments/perc_bongos.webp",
901    holdStyle: "congas",
902    scale: 1,
903    offsetX: 0,
904    offsetY: 0,
905    rotation: 0,
906    jointDef: getDefaultJointDef(-6, "congas")
907  },
908  triangle: {
909    program: -7,
910    name: "Triangle",
911    imgUrl: "/assets/visualizers/southpark/instruments/perc_triangle.webp",
912    holdStyle: "percussion_handheld",
913    scale: 1,
914    offsetX: 0,
915    offsetY: 0,
916    rotation: 0,
917    jointDef: getDefaultJointDef(-7, "percussion_handheld")
918  },
919  shaker: {
920    program: -8,
921    name: "Maracas & Shakers",
922    imgUrl: "/assets/visualizers/southpark/instruments/perc_shaker.webp",
923    holdStyle: "percussion_handheld",
924    scale: 1,
925    offsetX: 0,
926    offsetY: 0,
927    rotation: 0,
928    jointDef: getDefaultJointDef(-8, "percussion_handheld")
929  },
930  claps: {
931    program: -9,
932    name: "Hand Claps",
933    imgUrl: "",
934    holdStyle: "clapping",
935    scale: 1,
936    offsetX: 0,
937    offsetY: 0,
938    rotation: 0,
939    jointDef: getDefaultJointDef(-9, "clapping")
940  }
941};
942SOUTHPARK_INSTRUMENTS[-1] = PERCUSSION_INSTRUMENTS.drums;
943const getInstrumentDef = (program, isPercussion, percRole) => {
944  if (isPercussion) {
945    if (percRole && PERCUSSION_INSTRUMENTS[percRole]) {
946      return PERCUSSION_INSTRUMENTS[percRole];
947    }
948    return PERCUSSION_INSTRUMENTS.drums;
949  }
950  return SOUTHPARK_INSTRUMENTS[program] || SOUTHPARK_INSTRUMENTS[0];
951};
952class CalibrationStore {
953  constructor() {
954    this.state = {};
955    this.listeners = /* @__PURE__ */ new Set();
956  }
957  setOffsets(animalId, offsets) {
958    if (!this.state[animalId]) {
959      this.state[animalId] = {
960        headX: 0,
961        headY: 0,
962        armLeftX: 0,
963        armLeftY: 0,
964        armRightX: 0,
965        armRightY: 0,
966        scale: 1
967      };
968    }
969    this.state[animalId] = { ...this.state[animalId], ...offsets };
970    this.listeners.forEach((l) => l());
971  }
972  subscribe(listener) {
973    this.listeners.add(listener);
974    return () => {
975      this.listeners.delete(listener);
976    };
977  }
978}
979const calibrationStore = new CalibrationStore();
980function useCalibration(animalId) {
981  const [offsets, setOffsets] = reactExports.useState(calibrationStore.state[animalId]);
982  reactExports.useEffect(() => {
983    const update = () => setOffsets(calibrationStore.state[animalId]);
984    return calibrationStore.subscribe(update);
985  }, [animalId]);
986  return offsets;
987}
988const SP_STAN_BEANIE_SVG = `<g id="sp-stan-beanie">
989    <!-- Blue Cap Dome -->
990    <path d="M -44 -10 C -44 -46 -24 -62 0 -62 C 24 -62 44 -46 44 -10 Z" fill="#2563eb" stroke="#0f0f0f" stroke-width="3" stroke-linejoin="round" />
991    <!-- Red Folded Brim -->
992    <path d="M -48 -14 C -48 -4 -42 0 0 0 C 42 0 48 -4 48 -14 C 48 -22 42 -22 0 -22 C -42 -22 -48 -22 -48 -14 Z" fill="#dc2626" stroke="#0f0f0f" stroke-width="3" stroke-linejoin="round" />
993    <!-- Red Pompom on Top -->
994    <circle cx="0" cy="-66" r="11" fill="#dc2626" stroke="#0f0f0f" stroke-width="2.5" />
995    <path d="M -6 -70 Q 0 -63 6 -70 M -8 -65 Q 0 -59 8 -65" stroke="#b91c1c" stroke-width="1.8" fill="none" />
996</g>`;
997const SP_KYLE_USHANKA_SVG = `<g id="sp-kyle-ushanka">
998    <!-- Main Green Dome -->
999    <path d="M -46 -12 C -46 -54 -24 -65 0 -65 C 24 -65 46 -54 46 -12 Z" fill="#15803d" stroke="#0f0f0f" stroke-width="3" />
1000    <!-- Left Earflap -->
1001    <path d="M -48 -15 L -48 24 C -48 32 -34 32 -34 24 L -34 -15 Z" fill="#16a34a" stroke="#0f0f0f" stroke-width="2.8" stroke-linejoin="round" />
1002    <!-- Right Earflap -->
1003    <path d="M 34 -15 L 34 24 C 34 32 48 32 48 24 L 48 -15 Z" fill="#16a34a" stroke="#0f0f0f" stroke-width="2.8" stroke-linejoin="round" />
1004    <!-- Front Green Fold/Brim -->
1005    <rect x="-34" y="-30" width="68" height="22" rx="4" fill="#22c55e" stroke="#0f0f0f" stroke-width="2.8" />
1006    <!-- Stitching line -->
1007    <line x1="-30" y1="-20" x2="30" y2="-20" stroke="#15803d" stroke-width="1.5" stroke-dasharray="3 2" />
1008</g>`;
1009const SP_CARTMAN_BEANIE_SVG = `<g id="sp-cartman-beanie">
1010    <!-- Teal/Cyan Dome -->
1011    <path d="M -46 -8 C -46 -48 -24 -60 0 -60 C 24 -60 46 -48 46 -8 Z" fill="#06b6d4" stroke="#0f0f0f" stroke-width="3" stroke-linejoin="round" />
1012    <!-- Yellow Brim Band -->
1013    <path d="M -50 -12 C -50 -2 -42 2 0 2 C 42 2 50 -2 50 -12 C 50 -20 42 -20 0 -20 C -42 -20 -50 -20 -50 -12 Z" fill="#eab308" stroke="#0f0f0f" stroke-width="3" stroke-linejoin="round" />
1014    <!-- Yellow Pompom -->
1015    <circle cx="0" cy="-63" r="10" fill="#eab308" stroke="#0f0f0f" stroke-width="2.5" />
1016</g>`;
1017const SP_TOP_HAT_SVG = `<g id="sp-top-hat">
1018    <!-- Hat Brim -->
1019    <ellipse cx="0" cy="-2" rx="48" ry="10" fill="#18181b" stroke="#0f0f0f" stroke-width="3" />
1020    <!-- Crown Body -->
1021    <path d="M -30 -2 L -25 -52 L 25 -52 L 30 -2 Z" fill="#18181b" stroke="#0f0f0f" stroke-width="3" stroke-linejoin="round" />
1022    <!-- Crown Top Oval -->
1023    <ellipse cx="0" cy="-52" rx="25" ry="6" fill="#27272a" stroke="#0f0f0f" stroke-width="2.5" />
1024    <!-- Crimson Ribbon Band -->
1025    <path d="M -29.5 -8 L -28.5 -18 L 28.5 -18 L 29.5 -8 Z" fill="#dc2626" stroke="#0f0f0f" stroke-width="1.5" />
1026    <!-- Shiny satin highlight -->
1027    <line x1="-18" y1="-44" x2="-22" y2="-12" stroke="#ffffff" stroke-width="1.8" opacity="0.4" stroke-linecap="round" />
1028</g>`;
1029const SP_CHEF_HAT_SVG = `<g id="sp-chef-hat">
1030    <!-- Puffed Cloud Crown -->
1031    <path d="M -36 -20 C -52 -28 -50 -55 -30 -62 C -24 -75 24 -75 30 -62 C 50 -55 52 -28 36 -20 Z" fill="#f8fafc" stroke="#0f0f0f" stroke-width="3" stroke-linejoin="round" />
1032    <!-- Puff pleats / shadows -->
1033    <path d="M -18 -64 Q -15 -35 -14 -20" stroke="#cbd5e1" stroke-width="2" fill="none" />
1034    <path d="M 0 -68 Q 0 -38 0 -20" stroke="#cbd5e1" stroke-width="2" fill="none" />
1035    <path d="M 18 -64 Q 15 -35 14 -20" stroke="#cbd5e1" stroke-width="2" fill="none" />
1036    <!-- Base Headband -->
1037    <rect x="-32" y="-20" width="64" height="18" rx="2" fill="#ffffff" stroke="#0f0f0f" stroke-width="2.8" />
1038</g>`;
1039const SP_WIZARD_HAT_SVG = `<g id="sp-wizard-hat">
1040    <!-- Curved Cone -->
1041    <path d="M -40 -4 Q -10 -45 16 -78 Q 2 -55 40 -4 Z" fill="#4338ca" stroke="#0f0f0f" stroke-width="3" stroke-linejoin="round" />
1042    <!-- Brim -->
1043    <ellipse cx="0" cy="-4" rx="52" ry="11" fill="#3730a3" stroke="#0f0f0f" stroke-width="3" />
1044    <!-- Gold Ribbon Band -->
1045    <path d="M -36 -6 Q 0 -12 36 -6 L 33 -14 Q 0 -20 -33 -14 Z" fill="#facc15" stroke="#0f0f0f" stroke-width="1.8" />
1046    <!-- Yellow Stars -->
1047    <polygon points="5,-50 7,-44 13,-44 8,-40 10,-34 5,-38 0,-34 2,-40 -3,-44 3,-44" fill="#fef08a" stroke="#0f0f0f" stroke-width="1" />
1048    <polygon points="-14,-28 -12,-24 -7,-24 -11,-21 -9,-17 -14,-19 -18,-17 -16,-21 -20,-24 -15,-24" fill="#fef08a" stroke="#0f0f0f" stroke-width="1" />
1049</g>`;
1050const SP_COWBOY_HAT_SVG = `<g id="sp-cowboy-hat">
1051    <!-- Rolled Up Wide Brim -->
1052    <path d="M -56 -4 C -56 -16 -42 6 0 6 C 42 6 56 -16 56 -4 C 56 12 38 15 0 15 C -38 15 -56 12 -56 -4 Z" fill="#78350f" stroke="#0f0f0f" stroke-width="3" stroke-linejoin="round" />
1053    <!-- Creased Crown -->
1054    <path d="M -26 0 L -22 -36 C -18 -42 -8 -36 0 -40 C 8 -36 18 -42 22 -36 L 26 0 Z" fill="#92400e" stroke="#0f0f0f" stroke-width="3" stroke-linejoin="round" />
1055    <!-- Crown top pinch crease -->
1056    <path d="M -10 -38 Q 0 -34 10 -38" stroke="#451a03" stroke-width="2.5" fill="none" />
1057    <!-- Dark Leather Band with Buckle -->
1058    <rect x="-24" y="-8" width="48" height="7" rx="1" fill="#451a03" stroke="#0f0f0f" stroke-width="1.5" />
1059    <rect x="-4" y="-9" width="8" height="9" fill="#fbbf24" stroke="#0f0f0f" stroke-width="1.2" />
1060</g>`;
1061const SP_VIKING_HELMET_SVG = `<g id="sp-viking-helmet">
1062    <!-- Left Horn -->
1063    <path d="M -26 -14 C -45 -18 -55 -40 -46 -52 C -42 -44 -36 -28 -20 -24 Z" fill="#fef3c7" stroke="#0f0f0f" stroke-width="2.8" stroke-linejoin="round" />
1064    <!-- Right Horn -->
1065    <path d="M 26 -14 C 45 -18 55 -40 46 -52 C 42 -44 36 -28 20 -24 Z" fill="#fef3c7" stroke="#0f0f0f" stroke-width="2.8" stroke-linejoin="round" />
1066    <!-- Iron Dome -->
1067    <path d="M -36 -6 C -36 -38 -20 -44 0 -44 C 20 -44 36 -38 36 -6 Z" fill="#64748b" stroke="#0f0f0f" stroke-width="3" stroke-linejoin="round" />
1068    <!-- Center Nose Guard / Brow Band -->
1069    <rect x="-38" y="-8" width="76" height="10" rx="2" fill="#475569" stroke="#0f0f0f" stroke-width="2.5" />
1070    <rect x="-4" y="-8" width="8" height="20" rx="1" fill="#475569" stroke="#0f0f0f" stroke-width="2" />
1071    <!-- Rivets -->
1072    <circle cx="-28" cy="-3" r="2" fill="#cbd5e1" />
1073    <circle cx="-14" cy="-3" r="2" fill="#cbd5e1" />
1074    <circle cx="14" cy="-3" r="2" fill="#cbd5e1" />
1075    <circle cx="28" cy="-3" r="2" fill="#cbd5e1" />
1076</g>`;
1077const SP_ROYAL_CROWN_SVG = `<g id="sp-royal-crown">
1078    <!-- Golden Crown Peaks -->
1079    <polygon points="-38,0 -36,-32 -20,-16 0,-38 20,-16 36,-32 38,0" fill="#eab308" stroke="#0f0f0f" stroke-width="3" stroke-linejoin="round" />
1080    <!-- Headband base -->
1081    <rect x="-38" y="0" width="76" height="12" rx="2" fill="#ca8a04" stroke="#0f0f0f" stroke-width="2.5" />
1082    <!-- Gemstones -->
1083    <circle cx="-24" cy="6" r="3.5" fill="#dc2626" stroke="#0f0f0f" stroke-width="1.2" />
1084    <circle cx="0" cy="6" r="4.5" fill="#2563eb" stroke="#0f0f0f" stroke-width="1.2" />
1085    <circle cx="24" cy="6" r="3.5" fill="#16a34a" stroke="#0f0f0f" stroke-width="1.2" />
1086    <!-- Peak Pearl Jewels -->
1087    <circle cx="-36" cy="-32" r="3" fill="#fef08a" stroke="#0f0f0f" stroke-width="1.2" />
1088    <circle cx="0" cy="-38" r="4" fill="#fef08a" stroke="#0f0f0f" stroke-width="1.2" />
1089    <circle cx="36" cy="-32" r="3" fill="#fef08a" stroke="#0f0f0f" stroke-width="1.2" />
1090</g>`;
1091const SP_PARTY_HAT_SVG = `<g id="sp-party-hat">
1092    <!-- Cone Body -->
1093    <polygon points="-22,0 0,-56 22,0" fill="#ec4899" stroke="#0f0f0f" stroke-width="2.8" stroke-linejoin="round" />
1094    <!-- Diagonal Stripes -->
1095    <path d="M -16 -14 L 16 -14 L 11 -26 L -11 -26 Z" fill="#06b6d4" stroke="#0f0f0f" stroke-width="1.2" />
1096    <path d="M -7 -38 L 7 -38 L 4 -48 L -4 -48 Z" fill="#eab308" stroke="#0f0f0f" stroke-width="1.2" />
1097    <!-- Tassel / Pompom on Top -->
1098    <circle cx="0" cy="-58" r="6" fill="#facc15" stroke="#0f0f0f" stroke-width="2" />
1099</g>`;
1100const SP_BASEBALL_CAP_SVG = `<g id="sp-baseball-cap">
1101    <!-- Backward Visor -->
1102    <path d="M -36 -2 C -48 -6 -54 -10 -40 -16 C -30 -14 -20 -10 -16 -4 Z" fill="#b91c1c" stroke="#0f0f0f" stroke-width="2.5" stroke-linejoin="round" />
1103    <!-- Main Cap Dome -->
1104    <path d="M -34 -4 C -34 -38 -18 -44 0 -44 C 18 -44 34 -38 34 -4 Z" fill="#dc2626" stroke="#0f0f0f" stroke-width="3" stroke-linejoin="round" />
1105    <!-- White Front Panel -->
1106    <path d="M -18 -4 C -18 -32 -10 -38 0 -38 C 10 -38 18 -32 18 -4 Z" fill="#f8fafc" stroke="#0f0f0f" stroke-width="2" />
1107    <!-- Top Button -->
1108    <circle cx="0" cy="-44" r="3.5" fill="#991b1b" stroke="#0f0f0f" stroke-width="1.5" />
1109</g>`;
1110const SP_TAIKO_HEADBAND_RED_SVG = `<g id="sp-taiko-headband-red">
1111    <!-- Drop Shadow beneath headband -->
1112    <path d="M -34 7 C -17 11 17 11 34 7 C 34 9 17 13 -17 13 C -34 9 -34 7 -34 7 Z" fill="#000000" opacity="0.25" />
1113    <!-- Main Festive Red Cloth Band with Gentle Forehead Contour -->
1114    <path d="M -35 -4 C -18 -1 18 -1 35 -4 C 36 6 18 10 -18 10 C -36 6 -35 -4 -35 -4 Z" fill="#dc2626" stroke="#0f0f0f" stroke-width="2.5" stroke-linejoin="round" />
1115    <!-- Top Cloth Fold Highlight -->
1116    <path d="M -32 -2 C -16 1 16 1 32 -2" stroke="#f87171" stroke-width="1.6" fill="none" stroke-linecap="round" />
1117    <!-- White Bold Kanji '太鼓' Center Inscription -->
1118    <text x="0" y="5" font-family="'Segoe UI', Roboto, sans-serif" font-weight="900" font-size="7.5" fill="#ffffff" text-anchor="middle" letter-spacing="1.2">太鼓</text>
1119    <!-- Right-side Tied Hachimaki Knot -->
1120    <ellipse cx="35" cy="1" rx="4.5" ry="4" fill="#b91c1c" stroke="#0f0f0f" stroke-width="2" />
1121    <!-- Fluttering Ribbon Tail 1 -->
1122    <path d="M 36 3 C 42 8 44 17 40 23 C 37 23 38 14 34 5 Z" fill="#dc2626" stroke="#0f0f0f" stroke-width="2" stroke-linejoin="round" />
1123    <!-- Fluttering Ribbon Tail 2 -->
1124    <path d="M 34 2 C 39 6 42 12 41 18 C 38 18 37 11 32 4 Z" fill="#b91c1c" stroke="#0f0f0f" stroke-width="1.8" stroke-linejoin="round" />
1125</g>`;
1126const SP_TAIKO_HEADBAND_BLUE_SVG = `<g id="sp-taiko-headband-blue">
1127    <!-- Drop Shadow beneath headband -->
1128    <path d="M -34 7 C -17 11 17 11 34 7 C 34 9 17 13 -17 13 C -34 9 -34 7 -34 7 Z" fill="#000000" opacity="0.25" />
1129    <!-- Main Festive Blue Cloth Band with Gentle Forehead Contour -->
1130    <path d="M -35 -4 C -18 -1 18 -1 35 -4 C 36 6 18 10 -18 10 C -36 6 -35 -4 -35 -4 Z" fill="#2563eb" stroke="#0f0f0f" stroke-width="2.5" stroke-linejoin="round" />
1131    <!-- Top Cloth Fold Highlight -->
1132    <path d="M -32 -2 C -16 1 16 1 32 -2" stroke="#60a5fa" stroke-width="1.6" fill="none" stroke-linecap="round" />
1133    <!-- White Bold Kanji '太鼓' Center Inscription -->
1134    <text x="0" y="5" font-family="'Segoe UI', Roboto, sans-serif" font-weight="900" font-size="7.5" fill="#ffffff" text-anchor="middle" letter-spacing="1.2">太鼓</text>
1135    <!-- Right-side Tied Hachimaki Knot -->
1136    <ellipse cx="35" cy="1" rx="4.5" ry="4" fill="#1d4ed8" stroke="#0f0f0f" stroke-width="2" />
1137    <!-- Fluttering Ribbon Tail 1 -->
1138    <path d="M 36 3 C 42 8 44 17 40 23 C 37 23 38 14 34 5 Z" fill="#2563eb" stroke="#0f0f0f" stroke-width="2" stroke-linejoin="round" />
1139    <!-- Fluttering Ribbon Tail 2 -->
1140    <path d="M 34 2 C 39 6 42 12 41 18 C 38 18 37 11 32 4 Z" fill="#1d4ed8" stroke="#0f0f0f" stroke-width="1.8" stroke-linejoin="round" />
1141</g>`;
1142const SP_COOL_SUNGLASSES_SVG = `<g id="sp-cool-sunglasses">
1143    <!-- Frame Bar -->
1144    <line x1="-38" y1="-8" x2="38" y2="-8" stroke="#09090b" stroke-width="4.5" stroke-linecap="round" />
1145    <!-- Left Lens -->
1146    <polygon points="-36,-6 -10,-6 -14,14 -32,14" fill="#18181b" stroke="#000000" stroke-width="2.5" stroke-linejoin="round" />
1147    <!-- Right Lens -->
1148    <polygon points="10,-6 36,-6 32,14 14,14" fill="#18181b" stroke="#000000" stroke-width="2.5" stroke-linejoin="round" />
1149    <!-- Glint Lines (South park paper cutout shine) -->
1150    <line x1="-30" y1="-2" x2="-22" y2="10" stroke="#ffffff" stroke-width="2.5" opacity="0.65" stroke-linecap="round" />
1151    <line x1="16" y1="-2" x2="24" y2="10" stroke="#ffffff" stroke-width="2.5" opacity="0.65" stroke-linecap="round" />
1152</g>`;
1153const SP_NERD_GLASSES_SVG = `<g id="sp-nerd-glasses">
1154    <!-- Left Frame -->
1155    <circle cx="-20" cy="0" r="16" fill="#ffffff" fill-opacity="0.25" stroke="#0f0f0f" stroke-width="4" />
1156    <!-- Right Frame -->
1157    <circle cx="20" cy="0" r="16" fill="#ffffff" fill-opacity="0.25" stroke="#0f0f0f" stroke-width="4" />
1158    <!-- Bridge with White Tape -->
1159    <line x1="-6" y1="0" x2="6" y2="0" stroke="#0f0f0f" stroke-width="4" />
1160    <rect x="-4" y="-3" width="8" height="6" fill="#f8fafc" stroke="#0f0f0f" stroke-width="1.2" rx="1" />
1161</g>`;
1162const SP_VILLAIN_GOATEE_SVG = `<g id="sp-villain-goatee">
1163    <!-- Thin Pencil Mustache -->
1164    <path d="M -18 -8 Q -8 -4 0 -7 Q 8 -4 18 -8 Q 10 -2 0 -2 Q -10 -2 -18 -8 Z" fill="#0f0f0f" stroke="#0f0f0f" stroke-width="1.5" />
1165    <!-- Pointed Chin Goatee -->
1166    <polygon points="-8,4 8,4 0,22" fill="#0f0f0f" stroke="#0f0f0f" stroke-width="2" stroke-linejoin="round" />
1167</g>`;
1168const SP_PIRATE_EYEPATCH_SVG = `<g id="sp-pirate-eyepatch">
1169    <!-- Elastic Strap -->
1170    <line x1="-38" y1="-14" x2="38" y2="10" stroke="#0f0f0f" stroke-width="2.5" />
1171    <!-- Eyepatch Patch -->
1172    <path d="M 6 -4 C 8 -12 28 -8 30 2 C 30 14 10 16 6 -4 Z" fill="#18181b" stroke="#0f0f0f" stroke-width="2.5" stroke-linejoin="round" />
1173    <!-- White Skull Dot -->
1174    <circle cx="18" cy="2" r="3.5" fill="#f8fafc" />
1175    <rect x="16.5" y="4" width="3" height="2" fill="#f8fafc" />
1176</g>`;
1177const SP_MONOCLE_SVG = `<g id="sp-monocle">
1178    <!-- Monocle Ring -->
1179    <circle cx="18" cy="0" r="14" fill="#ffffff" fill-opacity="0.3" stroke="#eab308" stroke-width="3" />
1180    <!-- Wire Chain -->
1181    <path d="M 32 0 Q 36 18 28 34 Q 24 44 26 54" stroke="#ca8a04" stroke-width="1.8" fill="none" />
1182</g>`;
1183const SP_RED_BOW_TIE_SVG = `<g id="sp-red-bow-tie">
1184    <!-- Left Wing -->
1185    <polygon points="0,0 -22,-10 -20,10" fill="#dc2626" stroke="#0f0f0f" stroke-width="2.8" stroke-linejoin="round" />
1186    <!-- Right Wing -->
1187    <polygon points="0,0 22,-10 20,10" fill="#dc2626" stroke="#0f0f0f" stroke-width="2.8" stroke-linejoin="round" />
1188    <!-- Center Knot -->
1189    <rect x="-5" y="-6" width="10" height="12" rx="2" fill="#b91c1c" stroke="#0f0f0f" stroke-width="2.5" />
1190</g>`;
1191const SP_BLACK_BOW_TIE_SVG = `<g id="sp-black-bow-tie">
1192    <!-- Left Wing -->
1193    <polygon points="0,0 -24,-11 -22,11" fill="#0f172a" stroke="#0f0f0f" stroke-width="2.8" stroke-linejoin="round" />
1194    <!-- Right Wing -->
1195    <polygon points="0,0 24,-11 22,11" fill="#0f172a" stroke="#0f0f0f" stroke-width="2.8" stroke-linejoin="round" />
1196    <!-- Center Knot -->
1197    <rect x="-5" y="-6" width="10" height="12" rx="2" fill="#1e293b" stroke="#0f0f0f" stroke-width="2.5" />
1198</g>`;
1199const SP_STRIPED_SCARF_SVG = `<g id="sp-striped-scarf">
1200    <!-- Neck Wrap Loop -->
1201    <path d="M -34 -6 C -34 10 34 10 34 -6 C 36 -12 -36 -12 -34 -6 Z" fill="#dc2626" stroke="#0f0f0f" stroke-width="3" stroke-linejoin="round" />
1202    <!-- Stripes on loop -->
1203    <path d="M -18 -8 L -14 7" stroke="#f8fafc" stroke-width="5" />
1204    <path d="M 4 -9 L 8 7" stroke="#f8fafc" stroke-width="5" />
1205    <!-- Hanging Tail -->
1206    <path d="M 12 4 L 14 38 L 28 38 L 24 4 Z" fill="#dc2626" stroke="#0f0f0f" stroke-width="2.8" stroke-linejoin="round" />
1207    <path d="M 13 14 L 25 14" stroke="#f8fafc" stroke-width="4.5" />
1208    <path d="M 13 26 L 27 26" stroke="#f8fafc" stroke-width="4.5" />
1209    <!-- Fringe Tassels -->
1210    <line x1="16" y1="38" x2="16" y2="44" stroke="#f8fafc" stroke-width="2" />
1211    <line x1="20" y1="38" x2="20" y2="44" stroke="#dc2626" stroke-width="2" />
1212    <line x1="24" y1="38" x2="24" y2="44" stroke="#f8fafc" stroke-width="2" />
1213</g>`;
1214const SP_GOLD_CHAIN_SVG = `<g id="sp-gold-chain">
1215    <!-- Chain Arc -->
1216    <path d="M -30 -4 C -28 34 28 34 30 -4" stroke="#eab308" stroke-width="5" fill="none" stroke-linecap="round" />
1217    <path d="M -30 -4 C -28 34 28 34 30 -4" stroke="#ca8a04" stroke-width="2.5" stroke-dasharray="3 3" fill="none" />
1218    <!-- Big Gold Dollar Sign Medallion -->
1219    <g transform="translate(0, 38)">
1220        <circle cx="0" cy="0" r="16" fill="#facc15" stroke="#0f0f0f" stroke-width="2.8" />
1221        <text x="0" y="7" font-size="20" font-family="sans-serif" font-weight="900" fill="#713f12" text-anchor="middle">$</text>
1222    </g>
1223</g>`;
1224const SP_BANDANA_SVG = `<g id="sp-bandana">
1225    <!-- Triangle Scarf -->
1226    <polygon points="-32,-4 32,-4 0,26" fill="#dc2626" stroke="#0f0f0f" stroke-width="3" stroke-linejoin="round" />
1227    <!-- Knot on side -->
1228    <circle cx="28" cy="-4" r="4" fill="#b91c1c" stroke="#0f0f0f" stroke-width="1.8" />
1229    <!-- Paisley print dots -->
1230    <circle cx="-14" cy="4" r="1.5" fill="#f8fafc" />
1231    <circle cx="14" cy="4" r="1.5" fill="#f8fafc" />
1232    <circle cx="0" cy="14" r="1.5" fill="#f8fafc" />
1233</g>`;
1234const SP_TUXEDO_SUIT_SVG = `<g id="sp-tuxedo-suit">
1235    <!-- White Collared Shirt Inset -->
1236    <polygon points="-22,0 22,0 0,68" fill="#f8fafc" stroke="#0f0f0f" stroke-width="2" />
1237    <!-- Tuxedo Jacket Torso Shell -->
1238    <path d="M -54 -2 L -22 0 L 0 68 L 22 0 L 54 -2 L 50 82 L -50 82 Z" fill="#0f172a" stroke="#0f0f0f" stroke-width="3.5" stroke-linejoin="round" />
1239    <!-- Left Satin Lapel -->
1240    <polygon points="-22,0 -42,32 -10,48 -2,42" fill="#1e293b" stroke="#0f0f0f" stroke-width="2" />
1241    <!-- Right Satin Lapel -->
1242    <polygon points="22,0 42,32 10,48 2,42" fill="#1e293b" stroke="#0f0f0f" stroke-width="2" />
1243    <!-- Black Buttons -->
1244    <circle cx="0" cy="52" r="2.5" fill="#020617" />
1245    <circle cx="0" cy="64" r="2.5" fill="#020617" />
1246</g>`;
1247const SP_BROWN_VEST_SVG = `<g id="sp-brown-vest">
1248    <!-- White Undershirt V -->
1249    <polygon points="-18,0 18,0 0,44" fill="#f1f5f9" />
1250    <!-- Left Vest Panel -->
1251    <path d="M -50 0 L -18 0 L 0 44 L 0 80 L -46 80 Z" fill="#78350f" stroke="#0f0f0f" stroke-width="3" stroke-linejoin="round" />
1252    <!-- Right Vest Panel -->
1253    <path d="M 50 0 L 18 0 L 0 44 L 0 80 L 46 80 Z" fill="#78350f" stroke="#0f0f0f" stroke-width="3" stroke-linejoin="round" />
1254    <!-- Pockets -->
1255    <rect x="-34" y="52" width="14" height="4" rx="1" fill="#451a03" stroke="#0f0f0f" stroke-width="1.5" />
1256    <rect x="20" y="52" width="14" height="4" rx="1" fill="#451a03" stroke="#0f0f0f" stroke-width="1.5" />
1257    <!-- Gold Buttons -->
1258    <circle cx="0" cy="48" r="2.5" fill="#eab308" stroke="#0f0f0f" stroke-width="1" />
1259    <circle cx="0" cy="62" r="2.5" fill="#eab308" stroke="#0f0f0f" stroke-width="1" />
1260    <circle cx="0" cy="74" r="2.5" fill="#eab308" stroke="#0f0f0f" stroke-width="1" />
1261</g>`;
1262const SP_RED_PARKA_SVG = `<g id="sp-red-parka">
1263    <!-- Red Coat Body -->
1264    <path d="M -52 0 L 52 0 L 48 84 L -48 84 Z" fill="#dc2626" stroke="#0f0f0f" stroke-width="3.5" stroke-linejoin="round" />
1265    <!-- Black Center Seam Line -->
1266    <line x1="0" y1="0" x2="0" y2="84" stroke="#0f0f0f" stroke-width="3" />
1267    <!-- Yellow Buttons / Snaps -->
1268    <circle cx="-5" cy="22" r="3" fill="#facc15" stroke="#0f0f0f" stroke-width="1.5" />
1269    <circle cx="-5" cy="44" r="3" fill="#facc15" stroke="#0f0f0f" stroke-width="1.5" />
1270    <circle cx="-5" cy="66" r="3" fill="#facc15" stroke="#0f0f0f" stroke-width="1.5" />
1271</g>`;
1272const SP_GREEN_JACKET_SVG = `<g id="sp-green-jacket">
1273    <!-- Green Coat Body -->
1274    <path d="M -52 0 L 52 0 L 48 84 L -48 84 Z" fill="#16a34a" stroke="#0f0f0f" stroke-width="3.5" stroke-linejoin="round" />
1275    <!-- Bright Orange Collar -->
1276    <polygon points="-40,0 -18,18 0,6 18,18 40,0" fill="#ea580c" stroke="#0f0f0f" stroke-width="2.5" />
1277    <!-- Flap Pockets -->
1278    <rect x="-42" y="44" width="18" height="18" rx="2" fill="#15803d" stroke="#0f0f0f" stroke-width="2" />
1279    <polygon points="-44,44 -33,50 -22,44" fill="#ea580c" stroke="#0f0f0f" stroke-width="1.5" />
1280    <rect x="24" y="44" width="18" height="18" rx="2" fill="#15803d" stroke="#0f0f0f" stroke-width="2" />
1281    <polygon points="22,44 33,50 44,44" fill="#ea580c" stroke="#0f0f0f" stroke-width="1.5" />
1282</g>`;
1283const SP_WIZARD_ROBE_SVG = `<g id="sp-wizard-robe">
1284    <!-- Deep Indigo Robe -->
1285    <path d="M -52 0 L 52 0 L 58 92 L -58 92 Z" fill="#312e81" stroke="#0f0f0f" stroke-width="3.5" stroke-linejoin="round" />
1286    <!-- Golden Center Sash -->
1287    <rect x="-6" y="0" width="12" height="92" fill="#facc15" stroke="#0f0f0f" stroke-width="2" />
1288    <!-- Astrological Yellow Stars -->
1289    <polygon points="-32,30 -30,34 -25,34 -29,37 -27,41 -32,39 -37,41 -35,37 -39,34 -34,34" fill="#fef08a" stroke="#0f0f0f" stroke-width="1" />
1290    <polygon points="32,60 34,64 39,64 35,67 37,71 32,69 27,71 29,67 25,64 30,64" fill="#fef08a" stroke="#0f0f0f" stroke-width="1" />
1291</g>`;
1292const SP_SUPERHERO_CAPE_SVG = `<g id="sp-superhero-cape">
1293    <!-- Billowing Red Cape Wings -->
1294    <path d="M -40 6 C -75 30 -85 85 -70 115 C -45 110 -25 90 0 95 C 25 90 45 110 70 115 C 85 85 75 30 40 6 Z" fill="#dc2626" stroke="#0f0f0f" stroke-width="3.5" stroke-linejoin="round" />
1295    <!-- Yellow Clasp Collar Pin -->
1296    <circle cx="0" cy="8" r="7" fill="#facc15" stroke="#0f0f0f" stroke-width="2.5" />
1297    <polygon points="0,3 2,7 6,7 3,9 4,13 0,11 -4,13 -3,9 -6,7 -2,7" fill="#ca8a04" />
1298</g>`;
1299const DEFAULT_SOUTHPARK_COSTUMES = {
1300  // --- HATS & HEADWEAR (11 pieces) ---
1301  // Anchored to head group: translated to (headCenterX, headCenterY).
1302  // Brim/base rests snugly on the forehead (Y = -14 to -24) above the eyes (Y = 6).
1303  stan_beanie: {
1304    id: "stan_beanie",
1305    name: "Stan's Beanie",
1306    svgContent: SP_STAN_BEANIE_SVG,
1307    type: "hat",
1308    offsetX: 0,
1309    offsetY: -18,
1310    scale: 1.15,
1311    rotation: 0,
1312    anchorTo: "head"
1313  },
1314  kyle_ushanka: {
1315    id: "kyle_ushanka",
1316    name: "Kyle's Ushanka",
1317    svgContent: SP_KYLE_USHANKA_SVG,
1318    type: "hat",
1319    offsetX: 0,
1320    offsetY: -14,
1321    scale: 1.18,
1322    rotation: 0,
1323    anchorTo: "head"
1324  },
1325  cartman_beanie: {
1326    id: "cartman_beanie",
1327    name: "Cartman's Beanie",
1328    svgContent: SP_CARTMAN_BEANIE_SVG,
1329    type: "hat",
1330    offsetX: 0,
1331    offsetY: -18,
1332    scale: 1.16,
1333    rotation: 0,
1334    anchorTo: "head"
1335  },
1336  top_hat: {
1337    id: "top_hat",
1338    name: "Top Hat",
1339    svgContent: SP_TOP_HAT_SVG,
1340    url: "/assets/visualizers/southpark/costumes/top_hat.webp",
1341    type: "hat",
1342    offsetX: 0,
1343    offsetY: -22,
1344    scale: 1.08,
1345    rotation: -2,
1346    anchorTo: "head"
1347  },
1348  chef_hat: {
1349    id: "chef_hat",
1350    name: "Chef's Hat",
1351    svgContent: SP_CHEF_HAT_SVG,
1352    type: "hat",
1353    offsetX: 0,
1354    offsetY: -16,
1355    scale: 1.22,
1356    rotation: 0,
1357    anchorTo: "head"
1358  },
1359  wizard_hat: {
1360    id: "wizard_hat",
1361    name: "Grand Wizard Hat",
1362    svgContent: SP_WIZARD_HAT_SVG,
1363    type: "hat",
1364    offsetX: 2,
1365    offsetY: -20,
1366    scale: 1.18,
1367    rotation: 3,
1368    anchorTo: "head"
1369  },
1370  cowboy_hat: {
1371    id: "cowboy_hat",
1372    name: "Cowboy Stetson",
1373    svgContent: SP_COWBOY_HAT_SVG,
1374    type: "hat",
1375    offsetX: 0,
1376    offsetY: -18,
1377    scale: 1.18,
1378    rotation: 0,
1379    anchorTo: "head"
1380  },
1381  viking_helmet: {
1382    id: "viking_helmet",
1383    name: "Viking Helmet",
1384    svgContent: SP_VIKING_HELMET_SVG,
1385    type: "hat",
1386    offsetX: 0,
1387    offsetY: -14,
1388    scale: 1.18,
1389    rotation: 0,
1390    anchorTo: "head"
1391  },
1392  royal_crown: {
1393    id: "royal_crown",
1394    name: "Royal Crown",
1395    svgContent: SP_ROYAL_CROWN_SVG,
1396    type: "hat",
1397    offsetX: 0,
1398    offsetY: -20,
1399    scale: 1.15,
1400    rotation: 0,
1401    anchorTo: "head"
1402  },
1403  party_hat: {
1404    id: "party_hat",
1405    name: "Party Cone Hat",
1406    svgContent: SP_PARTY_HAT_SVG,
1407    type: "hat",
1408    offsetX: 0,
1409    offsetY: -26,
1410    scale: 1.12,
1411    rotation: 6,
1412    anchorTo: "head"
1413  },
1414  baseball_cap: {
1415    id: "baseball_cap",
1416    name: "Backward Cap",
1417    svgContent: SP_BASEBALL_CAP_SVG,
1418    type: "hat",
1419    offsetX: 0,
1420    offsetY: -18,
1421    scale: 1.18,
1422    rotation: 0,
1423    anchorTo: "head"
1424  },
1425  // --- EYEWEAR & FACIAL (5 pieces) ---
1426  // Anchored to head group: translated to (headCenterX, headCenterY).
1427  // Eyes center at Y = 6. Mouth / chin at Y = 24.
1428  cool_sunglasses: {
1429    id: "cool_sunglasses",
1430    name: "Cool Sunglasses",
1431    svgContent: SP_COOL_SUNGLASSES_SVG,
1432    url: "/assets/visualizers/southpark/costumes/cool_sunglasses.webp",
1433    type: "glasses",
1434    offsetX: 0,
1435    offsetY: 2,
1436    scale: 1.08,
1437    rotation: 0,
1438    anchorTo: "head"
1439  },
1440  nerd_glasses: {
1441    id: "nerd_glasses",
1442    name: "Nerd Glasses",
1443    svgContent: SP_NERD_GLASSES_SVG,
1444    type: "glasses",
1445    offsetX: 0,
1446    offsetY: 6,
1447    scale: 1.05,
1448    rotation: 0,
1449    anchorTo: "head"
1450  },
1451  villain_goatee: {
1452    id: "villain_goatee",
1453    name: "Evil Goatee & Mustache",
1454    svgContent: SP_VILLAIN_GOATEE_SVG,
1455    type: "glasses",
1456    offsetX: 0,
1457    offsetY: 24,
1458    scale: 1.05,
1459    rotation: 0,
1460    anchorTo: "head"
1461  },
1462  pirate_eyepatch: {
1463    id: "pirate_eyepatch",
1464    name: "Pirate Eyepatch",
1465    svgContent: SP_PIRATE_EYEPATCH_SVG,
1466    type: "glasses",
1467    offsetX: -1,
1468    offsetY: 4,
1469    scale: 1.08,
1470    rotation: -3,
1471    anchorTo: "head"
1472  },
1473  monocle: {
1474    id: "monocle",
1475    name: "Golden Monocle",
1476    svgContent: SP_MONOCLE_SVG,
1477    type: "glasses",
1478    offsetX: -1,
1479    offsetY: 6,
1480    scale: 1.02,
1481    rotation: 0,
1482    anchorTo: "head"
1483  },
1484  // --- NECKWEAR & COLLARS (5 pieces) ---
1485  // Anchored to body group: wrapped in <g transform="translate(0, 8)">.
1486  // Collar junction is at Y = 8 to 12.
1487  red_bow_tie: {
1488    id: "red_bow_tie",
1489    name: "Red Bow Tie",
1490    svgContent: SP_RED_BOW_TIE_SVG,
1491    url: "/assets/visualizers/southpark/costumes/red_bow_tie.webp",
1492    type: "neck",
1493    offsetX: 0,
1494    offsetY: 2,
1495    scale: 1.05,
1496    rotation: 0,
1497    anchorTo: "body"
1498  },
1499  black_bow_tie: {
1500    id: "black_bow_tie",
1501    name: "Black Bow Tie",
1502    svgContent: SP_BLACK_BOW_TIE_SVG,
1503    type: "neck",
1504    offsetX: 0,
1505    offsetY: 2,
1506    scale: 1.05,
1507    rotation: 0,
1508    anchorTo: "body"
1509  },
1510  striped_scarf: {
1511    id: "striped_scarf",
1512    name: "Striped Winter Scarf",
1513    svgContent: SP_STRIPED_SCARF_SVG,
1514    url: "/assets/visualizers/southpark/costumes/striped_scarf.webp",
1515    type: "neck",
1516    offsetX: 0,
1517    offsetY: 0,
1518    scale: 1.1,
1519    rotation: 0,
1520    anchorTo: "body"
1521  },
1522  gold_chain: {
1523    id: "gold_chain",
1524    name: "Gold Chain Medallion",
1525    svgContent: SP_GOLD_CHAIN_SVG,
1526    type: "neck",
1527    offsetX: 0,
1528    offsetY: 2,
1529    scale: 1,
1530    rotation: 0,
1531    anchorTo: "body"
1532  },
1533  bandana: {
1534    id: "bandana",
1535    name: "Western Bandana",
1536    svgContent: SP_BANDANA_SVG,
1537    type: "neck",
1538    offsetX: 0,
1539    offsetY: 4,
1540    scale: 1.08,
1541    rotation: 0,
1542    anchorTo: "body"
1543  },
1544  // --- BODY & TORSO OUTFITS (6 pieces) ---
1545  // Anchored to body group: wrapped in <g transform="translate(0, 10)">
1546  // (or <g transform="translate(0, 16)"> for superhero cape behind body).
1547  // Shoulders at Y = 10, waist at Y = 85 to 90.
1548  tuxedo_suit: {
1549    id: "tuxedo_suit",
1550    name: "Tuxedo Suit",
1551    svgContent: SP_TUXEDO_SUIT_SVG,
1552    type: "body",
1553    offsetX: 0,
1554    offsetY: -2,
1555    scale: 0.93,
1556    rotation: 0,
1557    anchorTo: "body"
1558  },
1559  brown_vest: {
1560    id: "brown_vest",
1561    name: "Brown Waistcoat",
1562    svgContent: SP_BROWN_VEST_SVG,
1563    url: "/assets/visualizers/southpark/costumes/brown_vest.webp",
1564    type: "body",
1565    offsetX: 0,
1566    offsetY: -2,
1567    scale: 0.94,
1568    rotation: 0,
1569    anchorTo: "body"
1570  },
1571  red_parka: {
1572    id: "red_parka",
1573    name: "Red Winter Parka",
1574    svgContent: SP_RED_PARKA_SVG,
1575    type: "body",
1576    offsetX: 0,
1577    offsetY: -2,
1578    scale: 0.96,
1579    rotation: 0,
1580    anchorTo: "body"
1581  },
1582  green_jacket: {
1583    id: "green_jacket",
1584    name: "Green Utility Jacket",
1585    svgContent: SP_GREEN_JACKET_SVG,
1586    type: "body",
1587    offsetX: 0,
1588    offsetY: -2,
1589    scale: 0.95,
1590    rotation: 0,
1591    anchorTo: "body"
1592  
1592},
1593  wizard_robe: {
1594    id: "wizard_robe",
1595    name: "Grand Wizard Robe",
1596    svgContent: SP_WIZARD_ROBE_SVG,
1597    type: "body",
1598    offsetX: 0,
1599    offsetY: -2,
1600    scale: 0.95,
1601    rotation: 0,
1602    anchorTo: "body"
1603  },
1604  superhero_cape: {
1605    id: "superhero_cape",
1606    name: "Superhero Cape",
1607    svgContent: SP_SUPERHERO_CAPE_SVG,
1608    type: "body",
1609    offsetX: 0,
1610    offsetY: -12,
1611    scale: 1.05,
1612    rotation: 0,
1613    anchorTo: "body",
1614    behindBody: true
1615  },
1616  taiko_headband: {
1617    id: "taiko_headband",
1618    name: "Taiko Festival Headband (Red)",
1619    svgContent: SP_TAIKO_HEADBAND_RED_SVG,
1620    type: "hat",
1621    offsetX: 0,
1622    offsetY: -4,
1623    scale: 0.9,
1624    rotation: 0,
1625    anchorTo: "head"
1626  },
1627  taiko_headband_blue: {
1628    id: "taiko_headband_blue",
1629    name: "Taiko Festival Headband (Blue)",
1630    svgContent: SP_TAIKO_HEADBAND_BLUE_SVG,
1631    type: "hat",
1632    offsetX: 0,
1633    offsetY: -4,
1634    scale: 0.9,
1635    rotation: 0,
1636    anchorTo: "head"
1637  }
1638};
1639function getCostumeDef(id, customCostumes) {
1640  if (!id) return null;
1641  if (customCostumes && customCostumes[id]) {
1642    return {
1643      ...DEFAULT_SOUTHPARK_COSTUMES[id] || {},
1644      ...customCostumes[id]
1645    };
1646  }
1647  return DEFAULT_SOUTHPARK_COSTUMES[id] || null;
1648}
1649function resolveCostumesList(costumes, customCostumes) {
1650  if (!costumes || !Array.isArray(costumes)) return [];
1651  return costumes.map((c) => {
1652    if (typeof c === "string") {
1653      return getCostumeDef(c, customCostumes);
1654    }
1655    if (c && typeof c === "object" && c.id) {
1656      const base = getCostumeDef(c.id, customCostumes);
1657      return { ...base || {}, ...c };
1658    }
1659    return null;
1660  }).filter((c) => c !== null);
1661}
1662const ELBOW_LIMITS = {
1663  left: { min: -170, max: 170 },
1664  right: { min: -170, max: 170 }
1665};
1666const SHOULDER_LIMITS = {
1667  left: { min: -150, max: 150 },
1668  right: { min: -150, max: 150 }
1669};
1670const WRIST_LIMITS = {
1671  left: { min: -45, max: 45 },
1672  right: { min: -45, max: 45 }
1673};
1674function clampElbowAngle(angleDeg, side) {
1675  const limits = ELBOW_LIMITS[side];
1676  if (isNaN(angleDeg)) return 0;
1677  return Math.max(limits.min, Math.min(limits.max, angleDeg));
1678}
1679function clampShoulderAngle(angleDeg, side) {
1680  const limits = SHOULDER_LIMITS[side];
1681  if (isNaN(angleDeg)) return 0;
1682  return Math.max(limits.min, Math.min(limits.max, angleDeg));
1683}
1684function clampWristAngle(angleDeg, side = "left") {
1685  const limits = WRIST_LIMITS[side];
1686  if (isNaN(angleDeg)) return 0;
1687  return Math.max(limits.min, Math.min(limits.max, angleDeg));
1688}
1689const NATURAL_PERFORMANCE_POSES = {
1690  standard: {
1691    targetL: { x: -34, y: 44 },
1692    targetR: { x: 34, y: 44 },
1693    bendL: 1,
1694    bendR: -1,
1695    wristTiltL: 0,
1696    wristTiltR: 0,
1697    bodyBounceFactor: -1.5,
1698    bodySquashFactor: 1
1699  },
1700  guitar: {
1701    targetL: { x: -12, y: 40 },
1702    // Strumming hand over sound hole
1703    targetR: { x: 18, y: 16 },
1704    // Fret hand on guitar neck
1705    bendL: 1,
1706    bendR: -1,
1707    wristTiltL: 8,
1708    wristTiltR: -12,
1709    bodyBounceFactor: -3,
1710    bodySquashFactor: 1,
1711    fretTrack: { startX: 10, startY: 26, endX: 24, endY: 10 }
1712  },
1713  bowing: {
1714    targetL: { x: -14, y: 40 },
1715    // Bow hand holding bow frog at bridge height
1716    targetR: { x: 12, y: 16 },
1717    // Cello / bass neck hand on neck
1718    bendL: 1,
1719    bendR: -1,
1720    wristTiltL: 5,
1721    wristTiltR: -12,
1722    hasBow: true,
1723    bodyBounceFactor: -2,
1724    bodySquashFactor: 1,
1725    contactPoint: { x: -2, y: 38 },
1726    fretTrack: { startX: 4, startY: 24, endX: 12, endY: 6 }
1727  },
1728  drumming: {
1729    targetL: { x: -22, y: 38 },
1730    // Left drumstick
1731    targetR: { x: 22, y: 38 },
1732    // Right drumstick
1733    bendL: 1,
1734    bendR: -1,
1735    wristTiltL: -8,
1736    wristTiltR: 8,
1737    hasSticks: true,
1738    bodyBounceFactor: -4,
1739    bodySquashFactor: 1.04
1740  },
1741  sitting: {
1742    targetL: { x: -14, y: 35 },
1743    // Left paw resting on keybed
1744    targetR: { x: 14, y: 35 },
1745    // Right paw resting on keybed
1746    bendL: 1,
1747    bendR: -1,
1748    wristTiltL: -3,
1749    wristTiltR: 3,
1750    bodyBounceFactor: 2,
1751    bodySquashFactor: 1
1752  },
1753  blowing: {
1754    targetL: { x: -2, y: 16 },
1755    // Left hand on valves
1756    targetR: { x: 20, y: 14 },
1757    // Right hand supporting leadpipe / slide
1758    bendL: 1,
1759    bendR: -1,
1760    wristTiltL: 15,
1761    wristTiltR: -15,
1762    bodyBounceFactor: -2,
1763    bodySquashFactor: 1,
1764    instrumentRotation: -3,
1765    slideTrack: { minExtension: 0, maxExtension: 24, angle: 18 }
1766  },
1767  violin: {
1768    targetL: { x: -16, y: 32 },
1769    // Bow hand holding frog of bow across strings (lowered to chest/rib height)
1770    targetR: { x: 62, y: 4 },
1771    // Left hand extended outward cradling violin neck
1772    bendL: 1,
1773    bendR: 1,
1774    // Elbow hangs down naturally below violin neck
1775    wristTiltL: -10,
1776    wristTiltR: -15,
1777    // Palm cupped under fingerboard
1778    hasBow: true,
1779    bodyBounceFactor: -2.5,
1780    bodySquashFactor: 1,
1781    contactPoint: { x: 0, y: 18 },
1782    fretTrack: { startX: 56, startY: 6, endX: 66, endY: 2 }
1783  },
1784  pizzicato: {
1785    targetL: { x: 4, y: 20 },
1786    // Right paw plucking strings directly over fingerboard
1787    targetR: { x: 62, y: 4 },
1788    // Left paw reaching up to fingerboard neck
1789    bendL: 1,
1790    bendR: 1,
1791    wristTiltL: 14,
1792    wristTiltR: -15,
1793    hasBow: false,
1794    bodyBounceFactor: -1.5,
1795    bodySquashFactor: 1,
1796    contactPoint: { x: 4, y: 20 },
1797    fretTrack: { startX: 56, startY: 6, endX: 66, endY: 2 }
1798  },
1799  harp: {
1800    targetL: { x: -17, y: 36 },
1801    // Left paw plucking bass strings on soundbox
1802    targetR: { x: 10, y: 28 },
1803    // Right paw plucking treble strings directly on strings
1804    bendL: 1,
1805    bendR: -1,
1806    wristTiltL: 0,
1807    wristTiltR: 8,
1808    hasBow: false,
1809    bodyBounceFactor: 1.5,
1810    bodySquashFactor: 1
1811  },
1812  flute: {
1813    targetL: { x: -44, y: 2 },
1814    // Right paw extending outward to transverse keys on viewer's left
1815    targetR: { x: 6, y: -2 },
1816    // Left paw supporting embouchure at lips
1817    bendL: -1,
1818    bendR: 1,
1819    wristTiltL: -10,
1820    wristTiltR: 10,
1821    bodyBounceFactor: -1.5,
1822    bodySquashFactor: 1
1823  },
1824  percussion_handheld: {
1825    targetL: { x: -16, y: 30 },
1826    // Holding paw holding tambourine/bell/shaker
1827    targetR: { x: 14, y: 26 },
1828    // Striking or shaking paw
1829    bendL: 1,
1830    bendR: -1,
1831    wristTiltL: -6,
1832    wristTiltR: 12,
1833    bodyBounceFactor: -3,
1834    bodySquashFactor: 1.02
1835  },
1836  congas: {
1837    targetL: { x: -20, y: 34 },
1838    // Left paw slap on left conga
1839    targetR: { x: 20, y: 34 },
1840    // Right paw slap on right conga
1841    bendL: 1,
1842    bendR: -1,
1843    wristTiltL: -12,
1844    wristTiltR: 12,
1845    bodyBounceFactor: -3.5,
1846    bodySquashFactor: 1.04
1847  },
1848  clapping: {
1849    targetL: { x: -6, y: 24 },
1850    // Left paw clapping center
1851    targetR: { x: 6, y: 24 },
1852    // Right paw clapping center
1853    bendL: 1,
1854    bendR: -1,
1855    wristTiltL: 15,
1856    wristTiltR: -15,
1857    bodyBounceFactor: -2,
1858    bodySquashFactor: 1.02
1859  },
1860  accordion: {
1861    targetL: { x: -26, y: 34 },
1862    // Left paw on bass button board
1863    targetR: { x: 26, y: 34 },
1864    // Right paw on treble keys
1865    bendL: 1,
1866    bendR: -1,
1867    wristTiltL: -12,
1868    wristTiltR: 12,
1869    bodyBounceFactor: -2.5,
1870    bodySquashFactor: 1.02
1871  },
1872  harmonica: {
1873    targetL: { x: -11, y: 3 },
1874    // Left paw cupping left end at mouth
1875    targetR: { x: 11, y: 3 },
1876    // Right paw cupping right end at mouth
1877    bendL: 1,
1878    bendR: -1,
1879    wristTiltL: 18,
1880    wristTiltR: -18,
1881    bodyBounceFactor: -2,
1882    bodySquashFactor: 1
1883  }
1884};
1885function evaluateWaveform(waveform, phase) {
1886  const normPhase = (phase % (2 * Math.PI) + 2 * Math.PI) % (2 * Math.PI);
1887  switch (waveform) {
1888    case "abs_sine":
1889      return Math.abs(Math.sin(normPhase));
1890    case "triangle": {
1891      const frac = normPhase / (2 * Math.PI);
1892      return frac < 0.5 ? 4 * frac - 1 : 3 - 4 * frac;
1893    }
1894    case "sawtooth":
1895      return normPhase / Math.PI - 1;
1896    case "sine":
1897    default:
1898      return Math.sin(normPhase);
1899  }
1900}
1901function evaluateKeyframes(keyframes, progress) {
1902  if (!keyframes || keyframes.length === 0) return 0;
1903  if (keyframes.length === 1) return keyframes[0].value;
1904  const clampedP = Math.max(0, Math.min(1, progress));
1905  let k0 = keyframes[0];
1906  let k1 = keyframes[keyframes.length - 1];
1907  for (let i = 0; i < keyframes.length - 1; i++) {
1908    if (clampedP >= keyframes[i].time && clampedP <= keyframes[i + 1].time) {
1909      k0 = keyframes[i];
1910      k1 = keyframes[i + 1];
1911      break;
1912    }
1913  }
1914  const span = Math.max(1e-4, k1.time - k0.time);
1915  let t = (clampedP - k0.time) / span;
1916  if (k0.easing === "sine" || k1.easing === "sine") {
1917    t = (1 - Math.cos(t * Math.PI)) / 2;
1918  } else if (k0.easing === "ease-in-out") {
1919    t = t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
1920  }
1921  return k0.value + (k1.value - k0.value) * t;
1922}
1923function evaluateActionChannels(channels, currentPose, state, weight = 1) {
1924  if (weight <= 0) return currentPose;
1925  const out = {
1926    targetL: { ...currentPose.targetL },
1927    targetR: { ...currentPose.targetR },
1928    wristTiltL: currentPose.wristTiltL,
1929    wristTiltR: currentPose.wristTiltR,
1930    bodyBounce: currentPose.bodyBounce,
1931    bodySquash: currentPose.bodySquash,
1932    bodySwayX: currentPose.bodySwayX,
1933    instrumentRotation: currentPose.instrumentRotation,
1934    headTilt: currentPose.headTilt,
1935    headBobY: currentPose.headBobY,
1936    mouthOpen: currentPose.mouthOpen,
1937    salivaDrip: currentPose.salivaDrip || 0,
1938    sweatFly: currentPose.sweatFly || 0
1939  };
1940  const climaxEnergy = state.isClimaxing ? 1.35 : 1;
1941  for (const ch of channels) {
1942    let valX = 0;
1943    let valY = 0;
1944    let valScalar = 0;
1945    if (ch.driver === "wave" && ch.wave) {
1946      if (ch.wave.activeOnly && !state.isActive) continue;
1947      const freq = ch.wave.frequency ?? 1;
1948      const phase = state.beatPhase * freq + (ch.wave.phase ?? 0);
1949      const waveVal = evaluateWaveform(ch.wave.waveform, phase);
1950      const dynamicAmp = state.velocityEnvelope !== void 0 ? state.velocityEnvelope : state.intensity;
1951      const amp = (ch.wave.amplitudeBase ?? 0) + (ch.wave.amplitude ?? 0) * (state.isActive ? dynamicAmp : 0.4);
1952      const result = waveVal * amp * weight * climaxEnergy;
1953      if (ch.wave.axis === "x") valX = result;
1954      else if (ch.wave.axis === "y") valY = result;
1955      else if (ch.wave.axis === "both") {
1956        valX = Math.cos(phase) * (amp * 0.35) * weight * climaxEnergy;
1957        valY = result;
1958      } else {
1959        valScalar = result;
1960      }
1961    } else if (ch.driver === "density_pulse" && ch.densityPulse) {
1962      if (!state.isActive) continue;
1963      const dp = ch.densityPulse;
1964      const density = state.noteDensity || 0;
1965      const effectiveFreq = (dp.baseFrequency ?? 1) * (1 + density * (dp.densityFactor ?? 0.15));
1966      const phase = state.beatPhase * effectiveFreq + (dp.phase ?? 0);
1967      const waveVal = evaluateWaveform(dp.waveform || "sine", phase);
1968      const envAmp = state.velocityEnvelope !== void 0 ? state.velocityEnvelope : state.intensity || 0.5;
1969      const amp = (dp.amplitudeBase ?? 0) + (dp.amplitude ?? 0) * envAmp;
1970      const result = waveVal * amp * weight * climaxEnergy;
1971      if (dp.axis === "x") valX = result;
1972      else if (dp.axis === "y") valY = result;
1973      else if (dp.axis === "both") {
1974        valX = Math.cos(phase) * (amp * 0.35) * weight * climaxEnergy;
1975        valY = result;
1976      } else {
1977        valScalar = result;
1978      }
1979    } else if (ch.driver === "velocity_envelope" && ch.velocityEnvelope) {
1980      const ve = ch.velocityEnvelope;
1981      const env = state.velocityEnvelope !== void 0 ? state.velocityEnvelope : state.isActive ? state.intensity : 0;
1982      let scaled = env * ve.factor;
1983      if (ve.minOutput !== void 0) scaled = Math.max(ve.minOutput, scaled);
1984      if (ve.maxOutput !== void 0) scaled = Math.min(ve.maxOutput, scaled);
1985      const result = scaled * weight * climaxEnergy;
1986      if (ve.axis === "x") valX = result;
1987      else if (ve.axis === "y") valY = result;
1988      else valScalar = result;
1989    } else if (ch.driver === "articulation_switch" && ch.articulationSwitch) {
1990      if (!state.isActive) continue;
1991      const asw = ch.articulationSwitch;
1992      const art = state.articulationType || "sustained";
1993      let val = asw.sustainedValue ?? 0;
1994      if (art === "legato" && asw.legatoValue !== void 0) val = asw.legatoValue;
1995      else if (art === "staccato" && asw.staccatoValue !== void 0) val = asw.staccatoValue;
1996      else if (art === "tremolo" && asw.tremoloValue !== void 0) val = asw.tremoloValue;
1997      const result = val * weight * climaxEnergy;
1998      if (asw.axis === "x") valX = result;
1999      else if (asw.axis === "y") valY = result;
2000      else valScalar = result;
2001    } else if (ch.driver === "phrase_accent" && ch.phraseAccent) {
2002      const pa = ch.phraseAccent;
2003      const decay = state.phraseAttackDecay ?? (state.isPhraseAttack ? 1 : 0);
2004      if (decay > 0) {
2005        const result = pa.impulse * decay * weight;
2006        if (pa.axis === "x") valX = result;
2007        else if (pa.axis === "y") valY = result;
2008        else valScalar = result;
2009      }
2010    } else if (ch.driver === "polyphony_spread" && ch.polyphonySpread) {
2011      if (!state.isActive) continue;
2012      const ps = ch.polyphonySpread;
2013      const poly = Math.max(0, (state.polyphony || 1) - 1);
2014      let spread = poly * ps.spreadPerNote;
2015      if (ps.maxSpread !== void 0) spread = Math.min(ps.maxSpread, spread);
2016      const result = spread * weight;
2017      if (ps.axis === "x") valX = result;
2018      else if (ps.axis === "y") valY = result;
2019      else valScalar = result;
2020    } else if (ch.driver === "climax_boost" && ch.climaxBoost) {
2021      if (state.isClimaxing) {
2022        const cb = ch.climaxBoost;
2023        const result = cb.multiplier * weight;
2024        if (cb.axis === "x") valX = result;
2025        else if (cb.axis === "y") valY = result;
2026        else valScalar = result;
2027      }
2028    } else if (ch.driver === "pitch_map" && ch.pitchMap) {
2029      const p = ch.pitchMap;
2030      const pitch = state.activePitch || 60;
2031      const poly = state.polyphony || 1;
2032      if (p.handSplit && poly <= 1) {
2033        if (p.handSplit === "left" && pitch < 60) continue;
2034        if (p.handSplit === "right" && pitch >= 60) continue;
2035      }
2036      const norm = Math.max(0, Math.min(1, (pitch - p.midiLow) / Math.max(1, p.midiHigh - p.midiLow)));
2037      let mapped = p.outputLow + (p.outputHigh - p.outputLow) * norm;
2038      if (p.addVibrato && state.isActive) {
2039        const vibRate = p.vibratoRate ?? 5;
2040        const vibAmp = p.vibratoAmplitude ?? 1;
2041        mapped += Math.sin(state.beatPhase * vibRate) * vibAmp;
2042      }
2043      const result = mapped * weight;
2044      if (p.axis === "x") valX = result;
2045      else if (p.axis === "y") valY = result;
2046      else valScalar = result;
2047    } else if (ch.driver === "keyboard_strike" && ch.keyboardStrike) {
2048      const ks = ch.keyboardStrike;
2049      const poly = state.polyphony || 1;
2050      const pitch = state.activePitch || 60;
2051      const isSingleMelody = poly <= 1;
2052      let shouldStrike = false;
2053      if (state.isActive) {
2054        if (isSingleMelody) {
2055          if (ks.hand === "left" && pitch >= 60) shouldStrike = true;
2056          if (ks.hand === "right" && pitch < 60) shouldStrike = true;
2057          if (ks.hand === "both") shouldStrike = true;
2058        } else {
2059          shouldStrike = true;
2060        }
2061      }
2062      let depth = 0;
2063      if (shouldStrike) {
2064        const handPhaseOffset = ks.hand === "right" ? Math.PI * 0.35 : 0;
2065        const beatBounce = Math.sin(state.beatPhase * (isSingleMelody ? 2 : 1) + handPhaseOffset);
2066        const strokeWeight = 0.55 + 0.45 * Math.abs(beatBounce);
2067        depth = ks.strikeDepth * (state.intensity || 0.8) * strokeWeight;
2068      }
2069      const result = depth * weight * climaxEnergy;
2070      if (ks.axis === "x") valX = result;
2071      else valY = result;
2072    } else if (ch.driver === "velocity_scale" && ch.velocityScale) {
2073      if (ch.velocityScale.activeOnly && !state.isActive) continue;
2074      const factor = ch.velocityScale.factor;
2075      const result = factor * (state.isActive ? state.intensity : 0) * weight * climaxEnergy;
2076      if (ch.velocityScale.axis === "x") valX = result;
2077      else if (ch.velocityScale.axis === "y") valY = result;
2078      else valScalar = result;
2079    } else if (ch.driver === "keyframe" && ch.keyframes) {
2080      const progress = state.idleProgress ?? 0;
2081      const kfVal = evaluateKeyframes(ch.keyframes.frames, progress) * weight;
2082      valScalar = kfVal;
2083    } else if (ch.driver === "constant" && ch.constant) {
2084      const result = ch.constant.value * weight;
2085      if (ch.constant.axis === "x") valX = result;
2086      else if (ch.constant.axis === "y") valY = result;
2087      else valScalar = result;
2088    }
2089    switch (ch.target) {
2090      case "targetL":
2091        out.targetL.x += valX;
2092        out.targetL.y += valY;
2093        break;
2094      case "targetR":
2095        out.targetR.x += valX;
2096        out.targetR.y += valY;
2097        break;
2098      case "wristTiltL":
2099        out.wristTiltL += valScalar || valY;
2100        break;
2101      case "wristTiltR":
2102        out.wristTiltR += valScalar || valY;
2103        break;
2104      case "bodyBounce":
2105        out.bodyBounce += valScalar || valY;
2106        break;
2107      case "bodySquash":
2108        out.bodySquash += valScalar;
2109        break;
2110      case "bodySwayX":
2111        out.bodySwayX += valScalar || valX;
2112        break;
2113      case "instrumentRotation":
2114        out.instrumentRotation += valScalar;
2115        break;
2116      case "headTilt":
2117        out.headTilt += valScalar;
2118        break;
2119      case "headBobY":
2120        out.headBobY += valScalar || valY;
2121        break;
2122      case "mouthOpen":
2123        out.mouthOpen = Math.max(0, Math.min(1, out.mouthOpen + (valScalar || (state.isActive ? 1 : 0))));
2124        break;
2125      case "salivaDrip":
2126        out.salivaDrip = Math.max(0, Math.min(1, (out.salivaDrip || 0) + (valScalar !== void 0 ? valScalar : 1)));
2127        break;
2128      case "sweatFly":
2129        out.sweatFly = Math.max(0, Math.min(1, (out.sweatFly || 0) + (valScalar !== void 0 ? valScalar : 1)));
2130        break;
2131    }
2132  }
2133  return out;
2134}
2135const DEFAULT_HOLD_STYLE_ACTIONS = {
2136  guitar: [
2137    {
2138      id: "guitar_strum",
2139      name: "Musical Strumming Sweep",
2140      holdStyle: "guitar",
2141      trigger: "playing",
2142      channels: [
2143        {
2144          target: "targetL",
2145          driver: "density_pulse",
2146          densityPulse: { waveform: "sine", baseFrequency: 2, densityFactor: 0.12, amplitude: 14, amplitudeBase: 6, axis: "both" }
2147        },
2148        {
2149          target: "wristTiltL",
2150          driver: "density_pulse",
2151          densityPulse: { waveform: "sine", baseFrequency: 2, densityFactor: 0.12, amplitude: 22, axis: "y" }
2152        },
2153        {
2154          target: "wristTiltL",
2155          driver: "articulation_switch",
2156          articulationSwitch: { staccatoValue: -12, legatoValue: 6, sustainedValue: 0, axis: "y" }
2157        },
2158        {
2159          target: "targetL",
2160          driver: "phrase_accent",
2161          phraseAccent: { impulse: -8, axis: "y" }
2162        },
2163        {
2164          target: "bodyBounce",
2165          driver: "velocity_envelope",
2166          velocityEnvelope: { factor: -6.5 }
2167        },
2168        {
2169          target: "bodySwayX",
2170          driver: "density_pulse",
2171          densityPulse: { waveform: "sine", baseFrequency: 1, amplitude: 4, axis: "x" }
2172        }
2173      ]
2174    },
2175    {
2176      id: "guitar_fret_slide",
2177      name: "Neck Fret Position & Vibrato",
2178      holdStyle: "guitar",
2179      trigger: "playing",
2180      channels: [
2181        {
2182          target: "targetR",
2183          driver: "pitch_map",
2184          pitchMap: { midiLow: 36, midiHigh: 84, outputLow: 14, outputHigh: -14, axis: "x", addVibrato: true, vibratoRate: 5.5, vibratoAmplitude: 1.5 }
2185        },
2186        {
2187          target: "targetR",
2188          driver: "pitch_map",
2189          pitchMap: { midiLow: 36, midiHigh: 84, outputLow: -12, outputHigh: 12, axis: "y" }
2190        }
2191      ]
2192    }
2193  ],
2194  drumming: [
2195    {
2196      id: "drum_sticks_strike",
2197      name: "Drumstick Explosive Downbeat",
2198      holdStyle: "drumming",
2199      trigger: "playing",
2200      channels: [
2201        {
2202          target: "targetL",
2203          driver: "density_pulse",
2204          densityPulse: { waveform: "abs_sine", baseFrequency: 2, densityFactor: 0.2, amplitude: 16, axis: "y" }
2205        },
2206        {
2207          target: "targetR",
2208          driver: "density_pulse",
2209          densityPulse: { waveform: "abs_sine", baseFrequency: 2, densityFactor: 0.2, phase: Math.PI * 0.5, amplitude: -14, axis: "y" }
2210        },
2211        {
2212          target: "targetL",
2213          driver: "phrase_accent",
2214          phraseAccent: { impulse: 18, axis: "y" }
2215        },
2216        {
2217          target: "targetR",
2218          driver: "phrase_accent",
2219          phraseAccent: { impulse: -16, axis: "y" }
2220        },
2221        {
2222          target: "bodyBounce",
2223          driver: "velocity_envelope",
2224          velocityEnvelope: { factor: -10 }
2225        },
2226        {
2227          target: "bodyBounce",
2228          driver: "phrase_accent",
2229          phraseAccent: { impulse: -8 }
2230        },
2231        {
2232          target: "bodySquash",
2233          driver: "velocity_envelope",
2234          velocityEnvelope: { factor: 0.09 }
2235        },
2236        {
2237          target: "headTilt",
2238          driver: "density_pulse",
2239          densityPulse: { waveform: "sine", baseFrequency: 2, amplitude: -7 }
2240        }
2241      ]
2242    }
2243  ],
2244  bowing: [
2245    {
2246      id: "cello_bow_draw",
2247      name: "Expressive Cello Bow Draw",
2248      holdStyle: "bowing",
2249      trigger: "playing",
2250      channels: [
2251        {
2252          target: "targetL",
2253          driver: "density_pulse",
2254          densityPulse: { waveform: "sine", baseFrequency: 0.8, densityFactor: 0.15, amplitude: 16, amplitudeBase: 8, axis: "x" }
2255        },
2256        {
2257          target: "targetL",
2258          driver: "wave",
2259          wave: { waveform: "sine", frequency: 0.8, phase: Math.PI / 2, amplitude: 5.5, axis: "y", activeOnly: true }
2260        },
2261        {
2262          target: "targetL",
2263          driver: "articulation_switch",
2264          articulationSwitch: { staccatoValue: -6, legatoValue: 5, tremoloValue: 9, sustainedValue: 0, axis: "x" }
2265        },
2266        {
2267          target: "wristTiltL",
2268          driver: "density_pulse",
2269          densityPulse: { waveform: "sine", baseFrequency: 0.8, densityFactor: 0.15, amplitude: 20, axis: "y" }
2270        },
2271        {
2272          target: "targetR",
2273          driver: "pitch_map",
2274          pitchMap: { midiLow: 36, midiHigh: 84, outputLow: -8, outputHigh: 8, axis: "y", addVibrato: true, vibratoRate: 5.5, vibratoAmplitude: 1.8 }
2275        },
2276        {
2277          target: "bodyBounce",
2278          driver: "velocity_envelope",
2279          velocityEnvelope: { factor: -5.5 }
2280        },
2281        {
2282          target: "bodySwayX",
2283          driver: "density_pulse",
2284          densityPulse: { waveform: "sine", baseFrequency: 0.4, amplitude: 6, axis: "x" }
2285        },
2286        {
2287          target: "headTilt",
2288          driver: "density_pulse",
2289          densityPulse: { waveform: "sine", baseFrequency: 0.6, amplitude: 6 }
2290        }
2291      ]
2292    }
2293  ],
2294  violin: [
2295    {
2296      id: "violin_bow_phrase",
2297      name: "Fluid Violin Collarbone Phrase",
2298      holdStyle: "violin",
2299      trigger: "playing",
2300      channels: [
2301        {
2302          target: "targetL",
2303          driver: "density_pulse",
2304          densityPulse: { waveform: "sine", baseFrequency: 1.2, densityFactor: 0.15, amplitude: 15, amplitudeBase: 8, axis: "x" }
2305        },
2306        {
2307          target: "targetL",
2308          driver: "wave",
2309          wave: { waveform: "sine", frequency: 1.2, phase: Math.PI / 2, amplitude: 4.5, axis: "y", activeOnly: true }
2310        },
2311        {
2312          target: "targetL",
2313          driver: "articulation_switch",
2314          articulationSwitch: { staccatoValue: -6, legatoValue: 5, tremoloValue: 8.5, sustainedValue: 0, axis: "x" }
2315        },
2316        {
2317          target: "wristTiltL",
2318          driver: "density_pulse",
2319          densityPulse: { waveform: "sine", baseFrequency: 1.2, densityFactor: 0.15, amplitude: 24, axis: "y" }
2320        },
2321        {
2322          target: "targetR",
2323          driver: "pitch_map",
2324          pitchMap: { midiLow: 55, midiHigh: 95, outputLow: -7, outputHigh: 7, axis: "y", addVibrato: true, vibratoRate: 6, vibratoAmplitude: 2.2 }
2325        },
2326        {
2327          target: "bodyBounce",
2328          driver: "velocity_envelope",
2329          velocityEnvelope: { factor: -5.5 }
2330        },
2331        {
2332          target: "headTilt",
2333          driver: "density_pulse",
2334          densityPulse: { waveform: "sine", baseFrequency: 0.6, amplitude: 8 }
2335        }
2336      ]
2337    }
2338  ],
2339  sitting: [
2340    {
2341      id: "piano_keyboard_keys",
2342      name: "Dynamic Keyboard Register Span & Strokes",
2343      holdStyle: "sitting",
2344      trigger: "playing",
2345      channels: [
2346        {
2347          target: "targetL",
2348          driver: "pitch_map",
2349          pitchMap: { midiLow: 60, midiHigh: 108, outputLow: 0, outputHigh: -14, axis: "x", handSplit: "left" }
2350        },
2351        {
2352          target: "targetR",
2353          driver: "pitch_map",
2354          pitchMap: { midiLow: 21, midiHigh: 60, outputLow: 14, outputHigh: 0, axis: "x", handSplit: "right" }
2355        },
2356        {
2357          target: "targetL",
2358          driver: "polyphony_spread",
2359          polyphonySpread: { spreadPerNote: -2.5, maxSpread: -8, axis: "x" }
2360        },
2361        {
2362          target: "targetR",
2363          driver: "polyphony_spread",
2364          polyphonySpread: { spreadPerNote: 2.5, maxSpread: 8, axis: "x" }
2365        },
2366        {
2367          target: "targetL",
2368          driver: "keyboard_strike",
2369          keyboardStrike: { hand: "left", strikeDepth: 3.2 }
2370        },
2371        {
2372          target: "targetR",
2373          driver: "keyboard_strike",
2374          keyboardStrike: { hand: "right", strikeDepth: 3.2 }
2375        },
2376        {
2377          target: "bodyBounce",
2378          driver: "velocity_envelope",
2379          velocityEnvelope: { factor: -3.5 }
2380        },
2381        {
2382          target: "bodyBounce",
2383          driver: "phrase_accent",
2384          phraseAccent: { impulse: -3.5 }
2385        },
2386        {
2387          target: "bodySwayX",
2388          driver: "pitch_map",
2389          pitchMap: { midiLow: 21, midiHigh: 108, outputLow: 6, outputHigh: -6 }
2390        },
2391        {
2392          target: "headTilt",
2393          driver: "pitch_map",
2394          pitchMap: { midiLow: 21, midiHigh: 108, outputLow: 5, outputHigh: -5 }
2395        },
2396        {
2397          target: "headBobY",
2398          driver: "density_pulse",
2399          densityPulse: { waveform: "sine", baseFrequency: 1, amplitude: 3.5 }
2400        }
2401      ]
2402    }
2403  ],
2404  blowing: [
2405    {
2406      id: "wind_brass_embouchure",
2407      name: "Brass Swelling Embouchure & Valve Rhythm",
2408      holdStyle: "blowing",
2409      trigger: "playing",
2410      channels: [
2411        {
2412          target: "targetL",
2413          driver: "density_pulse",
2414          densityPulse: { waveform: "abs_sine", baseFrequency: 2, densityFactor: 0.2, amplitude: -5.5, axis: "y" }
2415        },
2416        {
2417          target: "targetR",
2418          driver: "density_pulse",
2419          densityPulse: { waveform: "abs_sine", baseFrequency: 2, densityFactor: 0.2, amplitude: -5.5, axis: "y" }
2420        },
2421        {
2422          target: "mouthOpen",
2423          driver: "velocity_envelope",
2424          velocityEnvelope: { factor: 1 }
2425        },
2426        {
2427          target: "mouthOpen",
2428          driver: "phrase_accent",
2429          phraseAccent: { impulse: 0.35 }
2430        },
2431        {
2432          target: "headTilt",
2433          driver: "pitch_map",
2434          pitchMap: { midiLow: 48, midiHigh: 84, outputLow: -12, outputHigh: 6 }
2435        },
2436        {
2437          target: "bodyBounce",
2438          driver: "velocity_envelope",
2439          velocityEnvelope: { factor: -5.5 }
2440        }
2441      ]
2442    }
2443  ],
2444  flute: [
2445    {
2446      id: "flute_transverse_breath",
2447      name: "Side Transverse Flute Breath & Trill",
2448      holdStyle: "flute",
2449      trigger: "playing",
2450      channels: [
2451        {
2452          target: "targetL",
2453          driver: "density_pulse",
2454          densityPulse: { waveform: "sine", baseFrequency: 1.5, densityFactor: 0.15, amplitude: -4, axis: "y" }
2455        },
2456        {
2457          target: "targetR",
2458          driver: "density_pulse",
2459          densityPulse: { waveform: "sine", baseFrequency: 1.5, densityFactor: 0.15, amplitude: -4, axis: "y" }
2460        },
2461        {
2462          target: "mouthOpen",
2463          driver: "velocity_envelope",
2464          velocityEnvelope: { factor: 0.9 }
2465        },
2466        {
2467          target: "instrumentRotation",
2468          driver: "wave",
2469          wave: { waveform: "sine", frequency: 1, amplitude: 5.5, axis: "x", activeOnly: true }
2470        },
2471        {
2472          target: "headTilt",
2473          driver: "density_pulse",
2474          densityPulse: { waveform: "sine", baseFrequency: 0.7, amplitude: 6.5 }
2475        },
2476        {
2477          target: "bodyBounce",
2478          driver: "velocity_envelope",
2479          velocityEnvelope: { factor: -4 }
2480        }
2481      ]
2482    }
2483  ],
2484  congas: [
2485    {
2486      id: "congas_alternating_slaps",
2487      name: "Congas Alternating Hand Slaps",
2488      holdStyle: "congas",
2489      trigger: "playing",
2490      channels: [
2491        {
2492          target: "targetL",
2493          driver: "density_pulse",
2494          densityPulse: { waveform: "sine", baseFrequency: 2, densityFactor: 0.15, amplitude: 16, axis: "y" }
2495        },
2496        {
2497          target: "targetR",
2498          driver: "density_pulse",
2499          densityPulse: { waveform: "sine", baseFrequency: 2, densityFactor: 0.15, phase: Math.PI, amplitude: 16, axis: "y" }
2500        },
2501        {
2502          target: "targetL",
2503          driver: "phrase_accent",
2504          phraseAccent: { impulse: 10, axis: "y" }
2505        },
2506        {
2507          target: "bodyBounce",
2508          driver: "velocity_envelope",
2509          velocityEnvelope: { factor: -7.5 }
2510        },
2511        {
2512          target: "bodySquash",
2513          driver: "velocity_envelope",
2514          velocityEnvelope: { factor: 0.08 }
2515        },
2516        {
2517          target: "headTilt",
2518          driver: "density_pulse",
2519          densityPulse: { waveform: "sine", baseFrequency: 1.5, amplitude: 6 }
2520        }
2521      ]
2522    }
2523  ],
2524  percussion_handheld: [
2525    {
2526      id: "auxiliary_handheld_shake",
2527      name: "Rhythmic Handheld Strike / Shake",
2528      holdStyle: "percussion_handheld",
2529      trigger: "playing",
2530      channels: [
2531        {
2532          target: "targetL",
2533          driver: "density_pulse",
2534          densityPulse: { waveform: "sine", baseFrequency: 2, densityFactor: 0.2, amplitude: 8, axis: "y" }
2535        },
2536        {
2537          target: "targetR",
2538          driver: "density_pulse",
2539          densityPulse: { waveform: "sine", baseFrequency: 2, densityFactor: 0.2, phase: Math.PI, amplitude: 12, axis: "y" }
2540        },
2541        {
2542          target: "targetR",
2543          driver: "phrase_accent",
2544          phraseAccent: { impulse: 8, axis: "y" }
2545        },
2546        {
2547          target: "bodyBounce",
2548          driver: "velocity_envelope",
2549          velocityEnvelope: { factor: -5.5 }
2550        }
2551      ]
2552    }
2553  ],
2554  clapping: [
2555    {
2556      id: "clapping_applause_burst",
2557      name: "Enthusiastic Rhythmic Clapping",
2558      holdStyle: "clapping",
2559      trigger: "playing",
2560      channels: [
2561        {
2562          target: "targetL",
2563          driver: "density_pulse",
2564          densityPulse: { waveform: "abs_sine", baseFrequency: 2, densityFactor: 0.15, amplitude: -12, axis: "x" }
2565        },
2566        {
2567          target: "targetR",
2568          driver: "density_pulse",
2569          densityPulse: { waveform: "abs_sine", baseFrequency: 2, densityFactor: 0.15, amplitude: 12, axis: "x" }
2570        },
2571        {
2572          target: "bodyBounce",
2573          driver: "velocity_envelope",
2574          velocityEnvelope: { factor: -5.5 }
2575        }
2576      ]
2577    }
2578  ],
2579  accordion: [
2580    {
2581      id: "accordion_bellows_pump",
2582      name: "Accordion Bellows Expansion & Compression",
2583      holdStyle: "accordion",
2584      trigger: "playing",
2585      channels: [
2586        {
2587          target: "targetL",
2588          driver: "density_pulse",
2589          densityPulse: { waveform: "sine", baseFrequency: 1, densityFactor: 0.1, amplitude: -22, axis: "x" }
2590        },
2591        {
2592          target: "targetR",
2593          driver: "density_pulse",
2594          densityPulse: { waveform: "sine", baseFrequency: 1, densityFactor: 0.1, amplitude: 12, axis: "x" }
2595        },
2596        {
2597          target: "bodyBounce",
2598          driver: "velocity_envelope",
2599          velocityEnvelope: { factor: -5 }
2600        },
2601        {
2602          target: "bodySwayX",
2603          driver: "density_pulse",
2604          densityPulse: { waveform: "sine", baseFrequency: 0.5, amplitude: 6.5 }
2605        }
2606      ]
2607    }
2608  ],
2609  harmonica: [
2610    {
2611      id: "harmonica_blues_vibrato",
2612      name: "Harmonica Hand Wah & Head Tilt",
2613      holdStyle: "harmonica",
2614      trigger: "playing",
2615      channels: [
2616        {
2617          target: "targetL",
2618          driver: "density_pulse",
2619          densityPulse: { waveform: "sine", baseFrequency: 2, amplitude: -4.5, axis: "x" }
2620        },
2621        {
2622          target: "targetR",
2623          driver: "density_pulse",
2624          densityPulse: { waveform: "sine", baseFrequency: 2, amplitude: 4.5, axis: "x" }
2625        },
2626        {
2627          target: "headTilt",
2628          driver: "density_pulse",
2629          densityPulse: { waveform: "sine", baseFrequency: 1.5, amplitude: 9 }
2630        },
2631        {
2632          target: "bodyBounce",
2633          driver: "velocity_envelope",
2634          velocityEnvelope: { factor: -4 }
2635        }
2636      ]
2637    }
2638  ],
2639  harp: [
2640    {
2641      id: "harp_glissando_sweep",
2642      name: "Harp Arpeggio Plucking Sweep",
2643      holdStyle: "harp",
2644      trigger: "playing",
2645      channels: [
2646        {
2647          target: "targetL",
2648          driver: "density_pulse",
2649          densityPulse: { waveform: "sine", baseFrequency: 1.5, amplitude: 15, axis: "y" }
2650        },
2651        {
2652          target: "targetR",
2653          driver: "density_pulse",
2654          densityPulse: { waveform: "sine", baseFrequency: 1.5, phase: Math.PI * 0.5, amplitude: 15, axis: "y" }
2655        },
2656        {
2657          target: "targetL",
2658          driver: "polyphony_spread",
2659          polyphonySpread: { spreadPerNote: -3, maxSpread: -9, axis: "x" }
2660        },
2661        {
2662          target: "targetR",
2663          driver: "polyphony_spread",
2664          polyphonySpread: { spreadPerNote: 3, maxSpread: 9, axis: "x" }
2665        },
2666        {
2667          target: "bodyBounce",
2668          driver: "velocity_envelope",
2669          velocityEnvelope: { factor: -4.5 }
2670        }
2671      ]
2672    }
2673  ],
2674  pizzicato: [
2675    {
2676      id: "pizzicato_pluck_snap",
2677      name: "Pizzicato Pluck & Snap",
2678      holdStyle: "pizzicato",
2679      trigger: "playing",
2680      channels: [
2681        {
2682          target: "targetL",
2683          driver: "density_pulse",
2684          densityPulse: { waveform: "abs_sine", baseFrequency: 2, densityFactor: 0.15, amplitude: -8, axis: "y" }
2685        },
2686        {
2687          target: "wristTiltL",
2688          driver: "density_pulse",
2689          densityPulse: { waveform: "sine", baseFrequency: 2, amplitude: 26, axis: "y" }
2690        },
2691        {
2692          target: "targetR",
2693          driver: "pitch_map",
2694          pitchMap: { midiLow: 55, midiHigh: 95, outputLow: -6, outputHigh: 6, axis: "y" }
2695        },
2696        {
2697          target: "bodyBounce",
2698          driver: "phrase_accent",
2699          phraseAccent: { impulse: -6 }
2700        }
2701      ]
2702    }
2703  ],
2704  standard: [
2705    {
2706      id: "standard_vocal_sway",
2707      name: "Neutral Posture & Musical Breath Pulse",
2708      holdStyle: "standard",
2709      trigger: "playing",
2710      channels: [
2711        {
2712          target: "targetL",
2713          driver: "velocity_envelope",
2714          velocityEnvelope: { factor: 7, axis: "y" }
2715        },
2716        {
2717          target: "targetR",
2718          driver: "velocity_envelope",
2719          velocityEnvelope: { factor: 7, axis: "y" }
2720        },
2721        {
2722          target: "bodyBounce",
2723          driver: "velocity_envelope",
2724          velocityEnvelope: { factor: -4.5 }
2725        },
2726        {
2727          target: "headBobY",
2728          driver: "density_pulse",
2729          densityPulse: { waveform: "sine", baseFrequency: 1, amplitude: 4.5 }
2730        }
2731      ]
2732    }
2733  ]
2734};
2735const PROGRAM_SPECIFIC_ACTIONS = {
2736  // 57: Trombone (Slide extension dynamically tracking pitch + punchy slide accents)
2737  57: [
2738    {
2739      id: "trombone_slide_action",
2740      name: "Trombone Dynamic Slide Extension & Accents",
2741      program: 57,
2742      trigger: "playing",
2743      channels: [
2744        {
2745          target: "targetR",
2746          driver: "pitch_map",
2747          pitchMap: { midiLow: 48, midiHigh: 72, outputLow: 28, outputHigh: 0, axis: "x" }
2748        },
2749        {
2750          target: "targetR",
2751          driver: "pitch_map",
2752          pitchMap: { midiLow: 48, midiHigh: 72, outputLow: 8, outputHigh: 0, axis: "y" }
2753        },
2754        {
2755          target: "targetR",
2756          driver: "phrase_accent",
2757          phraseAccent: { impulse: 7, axis: "x" }
2758        },
2759        {
2760          target: "targetL",
2761          driver: "velocity_envelope",
2762          velocityEnvelope: { factor: -3.5, axis: "y" }
2763        },
2764        {
2765          target: "mouthOpen",
2766          driver: "velocity_envelope",
2767          velocityEnvelope: { factor: 1 }
2768        },
2769        {
2770          target: "mouthOpen",
2771          driver: "phrase_accent",
2772          phraseAccent: { impulse: 0.3 }
2773        },
2774        {
2775          target: "bodyBounce",
2776          driver: "velocity_envelope",
2777          velocityEnvelope: { factor: -6 }
2778        }
2779      ]
2780    }
2781  ],
2782  // 21, 23: Accordions (Horizontal bellows compression & expansion)
2783  21: [
2784    {
2785      id: "accordion_bellows_squeeze",
2786      name: "Accordion Bellows Expansion",
2787      program: 21,
2788      trigger: "playing",
2789      channels: [
2790        {
2791          target: "targetL",
2792          driver: "density_pulse",
2793          densityPulse: { waveform: "sine", baseFrequency: 1, densityFactor: 0.1, amplitude: -24, axis: "x" }
2794        },
2795        {
2796          target: "targetR",
2797          driver: "density_pulse",
2798          densityPulse: { waveform: "sine", baseFrequency: 1, densityFactor: 0.1, amplitude: 12, axis: "x" }
2799        },
2800        {
2801          target: "bodyBounce",
2802          driver: "velocity_envelope",
2803          velocityEnvelope: { factor: -5 }
2804        }
2805      ]
2806    }
2807  ],
2808  23: [
2809    {
2810      id: "tango_accordion_bellows",
2811      name: "Tango Accordion Passionate Bellows",
2812      program: 23,
2813      trigger: "playing",
2814      channels: [
2815        {
2816          target: "targetL",
2817          driver: "density_pulse",
2818          densityPulse: { waveform: "sine", baseFrequency: 1.5, densityFactor: 0.12, amplitude: -28, axis: "x" }
2819        },
2820        {
2821          target: "targetR",
2822          driver: "density_pulse",
2823          densityPulse: { waveform: "sine", baseFrequency: 1.5, densityFactor: 0.12, amplitude: 15, axis: "x" }
2824        },
2825        {
2826          target: "bodyBounce",
2827          driver: "velocity_envelope",
2828          velocityEnvelope: { factor: -6 }
2829        }
2830      ]
2831    }
2832  ],
2833  // 29, 30: Distortion / Overdriven Guitar (Heavy rock power-strum with head bang)
2834  29: [
2835    {
2836      id: "distortion_guitar_rock",
2837      name: "Heavy Rock Power Strum & Headbang",
2838      program: 29,
2839      trigger: "playing",
2840      channels: [
2841        {
2842          target: "targetL",
2843          driver: "density_pulse",
2844          densityPulse: { waveform: "sine", baseFrequency: 2, densityFactor: 0.15, amplitude: 11, amplitudeBase: 5, axis: "both" }
2845        },
2846        {
2847          target: "targetL",
2848          driver: "phrase_accent",
2849          phraseAccent: { impulse: -6, axis: "y" }
2850        },
2851        {
2852          target: "headTilt",
2853          driver: "density_pulse",
2854          densityPulse: { waveform: "abs_sine", baseFrequency: 2, densityFactor: 0.1, amplitude: -9, axis: "y" }
2855        },
2856        {
2857          target: "bodyBounce",
2858          driver: "velocity_envelope",
2859          velocityEnvelope: { factor: -5.5 }
2860        },
2861        {
2862          target: "targetR",
2863          driver: "pitch_map",
2864          pitchMap: { midiLow: 36, midiHigh: 84, outputLow: 6, outputHigh: -6, axis: "x" }
2865        }
2866      ]
2867    }
2868  ],
2869  30: [
2870    {
2871      id: "distortion_guitar_lead",
2872      name: "Distortion Guitar Lead Solo",
2873      program: 30,
2874      trigger: "playing",
2875      channels: [
2876        {
2877          target: "targetL",
2878          driver: "density_pulse",
2879          densityPulse: { waveform: "sine", baseFrequency: 3.5, densityFactor: 0.2, amplitude: 8.5, amplitudeBase: 4, axis: "both" }
2880        },
2881        {
2882          target: "targetR",
2883          driver: "pitch_map",
2884          pitchMap: { midiLow: 40, midiHigh: 90, outputLow: 8, outputHigh: -8, axis: "x", addVibrato: true, vibratoRate: 8, vibratoAmplitude: 1.8 }
2885        },
2886        {
2887          target: "headTilt",
2888          driver: "density_pulse",
2889          densityPulse: { waveform: "sine", baseFrequency: 1.5, amplitude: 6 }
2890        },
2891        {
2892          target: "bodyBounce",
2893          driver: "velocity_envelope",
2894          velocityEnvelope: { factor: -4.5 }
2895        }
2896      ]
2897    }
2898  ],
2899  // 36, 37: Slap Bass (Thumb slap down and aggressive pop)
2900  36: [
2901    {
2902      id: "slap_bass_pop",
2903      name: "Slap Bass Thumb Pop & Funk Bounce",
2904      program: 36,
2905      trigger: "playing",
2906      channels: [
2907        {
2908          target: "targetL",
2909          driver: "density_pulse",
2910          densityPulse: { waveform: "sine", baseFrequency: 2, densityFactor: 0.15, amplitude: 12.5, axis: "y" }
2911        },
2912        {
2913          target: "targetL",
2914          driver: "phrase_accent",
2915          phraseAccent: { impulse: 6, axis: "y" }
2916        },
2917        {
2918          target: "wristTiltL",
2919          driver: "density_pulse",
2920          densityPulse: { waveform: "sine", baseFrequency: 2, densityFactor: 0.15, amplitude: 22, axis: "y" }
2921        },
2922        {
2923          target: "bodyBounce",
2924          driver: "velocity_envelope",
2925          velocityEnvelope: { factor: -6.5 }
2926        }
2927      ]
2928    }
2929  ],
2930  // 44: Tremolo Strings (Rapid micro-flutter bow)
2931  44: [
2932    {
2933      id: "tremolo_bow_flutter",
2934      name: "Rapid Tremolo Bow Flutter",
2935      program: 44,
2936      trigger: "playing",
2937      channels: [
2938        {
2939          target: "targetL",
2940          driver: "density_pulse",
2941          densityPulse: { waveform: "sine", baseFrequency: 7, densityFactor: 0.25, amplitude: 6.5, axis: "x" }
2942        },
2943        {
2944          target: "wristTiltL",
2945          driver: "density_pulse",
2946          densityPulse: { waveform: "sine", baseFrequency: 7, densityFactor: 0.25, amplitude: 16, axis: "y" }
2947        },
2948        {
2949          target: "bodyBounce",
2950          driver: "velocity_envelope",
2951          velocityEnvelope: { factor: -1.8 }
2952        }
2953      ]
2954    }
2955  ],
2956  // 46: Orchestral Harp (Dual arm sweeping inward arpeggios)
2957  46: [
2958    {
2959      id: "harp_glissando_sweep",
2960      name: "Harp Arpeggio Plucking Sweep",
2961      program: 46,
2962      trigger: "playing",
2963      channels: [
2964        {
2965          target: "targetL",
2966          driver: "density_pulse",
2967          densityPulse: { waveform: "sine", baseFrequency: 1.5, amplitude: 8, axis: "y" }
2968        },
2969        {
2970          target: "targetR",
2971          driver: "density_pulse",
2972          densityPulse: { waveform: "sine", baseFrequency: 1.5, phase: Math.PI * 0.5, amplitude: 8, axis: "y" }
2973        },
2974        {
2975          target: "targetL",
2976          driver: "density_pulse",
2977          densityPulse: { waveform: "sine", baseFrequency: 0.75, amplitude: 6, axis: "x" }
2978        },
2979        {
2980          target: "targetR",
2981          driver: "density_pulse",
2982          densityPulse: { waveform: "sine", baseFrequency: 0.75, amplitude: -6, axis: "x" }
2983        },
2984        {
2985          target: "bodyBounce",
2986          driver: "velocity_envelope",
2987          velocityEnvelope: { factor: 1.2 }
2988        }
2989      ]
2990    }
2991  ],
2992  // 52, 53: Choir Aahs & Voice Oohs (Open mouth singing & chest posture)
2993  52: [
2994    {
2995      id: "choir_singing_mouth",
2996      name: "Choir Passionate Vocal Projection",
2997      program: 52,
2998      trigger: "playing",
2999      channels: [
3000        {
3001          target: "mouthOpen",
3002          driver: "velocity_envelope",
3003          velocityEnvelope: { factor: 1 }
3004        },
3005        {
3006          target: "mouthOpen",
3007          driver: "phrase_accent",
3008          phraseAccent: { impulse: 0.2 }
3009        },
3010        {
3011          target: "headTilt",
3012          driver: "constant",
3013          constant: { value: 6 }
3014        },
3015        {
3016          target: "bodyBounce",
3017          driver: "velocity_envelope",
3018          velocityEnvelope: { factor: -2 }
3019        }
3020      ]
3021    }
3022  ],
3023  53: [
3024    {
3025      id: "voice_oohs_mouth",
3026      name: "Voice Oohs Resonant Breath",
3027      program: 53,
3028      trigger: "playing",
3029      channels: [
3030        {
3031          target: "mouthOpen",
3032          driver: "velocity_envelope",
3033          velocityEnvelope: { factor: 0.85 }
3034        },
3035        {
3036          target: "headTilt",
3037          driver: "constant",
3038          constant: { value: 4 }
3039        }
3040      ]
3041    }
3042  ],
3043  // 55: Orchestra Hit (Conductor Baton Dynamic Tutti Hit)
3044  55: [
3045    {
3046      id: "orchestra_hit_downbeat",
3047      name: "Tutti Orchestra Hit Downbeat",
3048      program: 55,
3049      trigger: "playing",
3050      channels: [
3051        {
3052          target: "targetR",
3053          driver: "phrase_accent",
3054          phraseAccent: { impulse: -16, axis: "y" }
3055        },
3056        {
3057          target: "wristTiltR",
3058          driver: "phrase_accent",
3059          phraseAccent: { impulse: 22, axis: "y" }
3060        },
3061        {
3062          target: "bodyBounce",
3063          driver: "phrase_accent",
3064          phraseAccent: { impulse: -6 }
3065        },
3066        {
3067          target: "bodySquash",
3068          driver: "phrase_accent",
3069          phraseAccent: { impulse: 0.06 }
3070        }
3071      ]
3072    }
3073  ],
3074  // 104: Sitar (Steep angle fret bends)
3075  104: [
3076    {
3077      id: "sitar_twang_bend",
3078      name: "Sitar Sympathetic String Bends",
3079      program: 104,
3080      trigger: "playing",
3081      channels: [
3082        {
3083          target: "targetR",
3084          driver: "pitch_map",
3085          pitchMap: { midiLow: 40, midiHigh: 80, outputLow: 10, outputHigh: -8, axis: "x", addVibrato: true, vibratoRate: 4, vibratoAmplitude: 2 }
3086        },
3087        {
3088          target: "targetL",
3089          driver: "density_pulse",
3090          densityPulse: { waveform: "sine", baseFrequency: 1.5, densityFactor: 0.15, amplitude: 6.5, axis: "both" }
3091        },
3092        {
3093          target: "bodyBounce",
3094          driver: "velocity_envelope",
3095          velocityEnvelope: { factor: -2.8 }
3096        }
3097      ]
3098    }
3099  ]
3100};
3101const DEFAULT_IDLE_ACTIONS = [
3102  // 1. Idle Nod (Feeling the groove)
3103  {
3104    id: "idle_nod",
3105    name: "Idle Groove Nod",
3106    category: "idle",
3107    trigger: "idle",
3108    priority: 5,
3109    idleSchedule: {
3110      minIntervalMs: 8e3,
3111      maxIntervalMs: 22e3,
3112      durationMs: 2200,
3113      blendInMs: 400,
3114      blendOutMs: 400
3115    },
3116    channels: [
3117      {
3118        target: "headTilt",
3119        driver: "keyframe",
3120        keyframes: {
3121          frames: [
3122            { time: 0, value: 0 },
3123            { time: 0.15, value: 7, easing: "sine" },
3124            { time: 0.3, value: -2, easing: "sine" },
3125            { time: 0.45, value: 6, easing: "sine" },
3126            { time: 0.6, value: -1, easing: "sine" },
3127            { time: 0.75, value: 5, easing: "sine" },
3128            { time: 1, value: 0, easing: "sine" }
3129          ]
3130        }
3131      },
3132      {
3133        target: "headBobY",
3134        driver: "keyframe",
3135        keyframes: {
3136          frames: [
3137            { time: 0, value: 0 },
3138            { time: 0.15, value: 3.5, easing: "sine" },
3139            { time: 0.3, value: 0, easing: "linear" },
3140            { time: 0.45, value: 3, easing: "sine" },
3141            { time: 0.6, value: 0, easing: "linear" },
3142            { time: 0.75, value: 2.5, easing: "sine" },
3143            { time: 1, value: 0, easing: "linear" }
3144          ]
3145        }
3146      }
3147    ]
3148  },
3149  // 2. Idle Look Around (Surveying bandmates on stage)
3150  {
3151    id: "idle_look_around",
3152    name: "Idle Look Around Stage",
3153    category: "idle",
3154    trigger: "idle",
3155    priority: 4,
3156    idleSchedule: {
3157      minIntervalMs: 14e3,
3158      maxIntervalMs: 32e3,
3159      durationMs: 2800,
3160      blendInMs: 500,
3161      blendOutMs: 500
3162    },
3163    channels: [
3164      {
3165        target: "headTilt",
3166        driver: "keyframe",
3167        keyframes: {
3168          frames: [
3169            { time: 0, value: 0 },
3170            { time: 0.25, value: 12, easing: "sine" },
3171            { time: 0.55, value: 12, easing: "linear" },
3172            // hold glance
3173            { time: 0.75, value: -6, easing: "sine" },
3174            // glance other side
3175            { time: 1, value: 0, easing: "sine" }
3176          ]
3177        }
3178      },
3179      {
3180        target: "bodySwayX",
3181        driver: "keyframe",
3182        keyframes: {
3183          frames: [
3184            { time: 0, value: 0 },
3185            { time: 0.25, value: 2.5, easing: "sine" },
3186            { time: 0.55, value: 2.5, easing: "linear" },
3187            { time: 0.75, value: -1.5, easing: "sine" },
3188            { time: 1, value: 0, easing: "sine" }
3189          ]
3190        }
3191      }
3192    ]
3193  },
3194  // 3. Idle Ear/Head Twitch (Authentic South Park character micro-twitch)
3195  {
3196    id: "idle_ear_twitch",
3197    name: "Idle Micro-Twitch",
3198    category: "idle",
3199    trigger: "idle",
3200    priority: 6,
3201    idleSchedule: {
3202      minIntervalMs: 12e3,
3203      maxIntervalMs: 28e3,
3204      durationMs: 650,
3205      blendInMs: 100,
3206      blendOutMs: 150
3207    },
3208    channels: [
3209      {
3210        target: "headTilt",
3211        driver: "keyframe",
3212        keyframes: {
3213          frames: [
3214            { time: 0, value: 0 },
3215            { time: 0.2, value: 8, easing: "linear" },
3216            { time: 0.4, value: -6, easing: "linear" },
3217            { time: 0.6, value: 7, easing: "linear" },
3218            { time: 0.8, value: -3, easing: "linear" },
3219            { time: 1, value: 0, easing: "linear" }
3220          ]
3221        }
3222      }
3223    ]
3224  },
3225  // 4. Idle Yawn (Relaxed sigh with open mouth)
3226  {
3227    id: "idle_yawn",
3228    name: "Idle Restful Yawn",
3229    category: "idle",
3230    trigger: "idle",
3231    priority: 5,
3232    idleSchedule: {
3233      minIntervalMs: 25e3,
3234      maxIntervalMs: 6e4,
3235      durationMs: 3200,
3236      blendInMs: 600,
3237      blendOutMs: 600
3238    },
3239    channels: [
3240      {
3241        target: "mouthOpen",
3242        driver: "keyframe",
3243        keyframes: {
3244          frames: [
3245            { time: 0, value: 0 },
3246            { time: 0.25, value: 0.9, easing: "sine" },
3247            { time: 0.65, value: 1, easing: "linear" },
3248            { time: 0.85, value: 0.4, easing: "sine" },
3249            { time: 1, value: 0, easing: "sine" }
3250          ]
3251        }
3252      },
3253      {
3254        target: "headTilt",
3255        driver: "keyframe",
3256        keyframes: {
3257          frames: [
3258            { time: 0, value: 0 },
3259            { time: 0.3, value: -8, easing: "sine" },
3260            { time: 0.7, value: -8, easing: "linear" },
3261            { time: 1, value: 0, easing: "sine" }
3262          ]
3263        }
3264      },
3265      {
3266        target: "bodyBounce",
3267        driver: "keyframe",
3268        keyframes: {
3269          frames: [
3270            { time: 0, value: 0 },
3271            { time: 0.35, value: -3, easing: "sine" },
3272            { time: 0.75, value: -3, easing: "linear" },
3273            { time: 1, value: 0, easing: "sine" }
3274          ]
3275        }
3276      }
3277    ]
3278  },
3279  // 5. Idle Paw Scratch (Lifts one paw towards cheek / ear)
3280  {
3281    id: "idle_scratch",
3282    name: "Idle Paw Scratch",
3283    category: "idle",
3284    trigger: "idle",
3285    priority: 7,
3286    idleSchedule: {
3287      minIntervalMs: 2e4,
3288      maxIntervalMs: 5e4,
3289      durationMs: 1800,
3290      blendInMs: 300,
3291      blendOutMs: 350
3292    },
3293    channels: [
3294      {
3295        target: "targetR",
3296        driver: "keyframe",
3297        keyframes: {
3298          frames: [
3299            { time: 0, value: 0 },
3300            { time: 0.25, value: -12, easing: "sine" },
3301            // reach up toward snout/ear
3302            { time: 0.4, value: -14, easing: "linear" },
3303            { time: 0.55, value: -11, easing: "linear" },
3304            { time: 0.7, value: -14, easing: "linear" },
3305            { time: 1, value: 0, easing: "sine" }
3306          ]
3307        }
3308      },
3309      {
3310        target: "wristTiltR",
3311        driver: "keyframe",
3312        keyframes: {
3313          frames: [
3314            { time: 0, value: 0 },
3315            { time: 0.25, value: 25, easing: "sine" },
3316            { time: 0.4, value: 32, easing: "linear" },
3317            { time: 0.55, value: 20, easing: "linear" },
3318            { time: 0.7, value: 32, easing: "linear" },
3319            { time: 1, value: 0, easing: "sine" }
3320          ]
3321        }
3322      }
3323    ]
3324  },
3325  // 6. Idle Instrument Adjust (Tuning peg twist / horn valve check)
3326  {
3327    id: "idle_instrument_adjust",
3328    name: "Idle Instrument Grip Adjust",
3329    category: "idle",
3330    trigger: "idle",
3331    priority: 4,
3332    idleSchedule: {
3333      minIntervalMs: 18e3,
3334      maxIntervalMs: 45e3,
3335      durationMs: 1500,
3336      blendInMs: 350,
3337      blendOutMs: 350
3338    },
3339    channels: [
3340      {
3341        target: "targetL",
3342        driver: "keyframe",
3343        keyframes: {
3344          frames: [
3345            { time: 0, value: 0 },
3346            { time: 0.35, value: -6, easing: "sine" },
3347            { time: 0.65, value: -6, easing: "linear" },
3348            { time: 1, value: 0, easing: "sine" }
3349          ]
3350        }
3351      },
3352      {
3353        target: "wristTiltL",
3354        driver: "keyframe",
3355        keyframes: {
3356          frames: [
3357            { time: 0, value: 0 },
3358            { time: 0.35, value: 16, easing: "sine" },
3359            { time: 0.65, value: -8, easing: "linear" },
3360            { time: 1, value: 0, easing: "sine" }
3361          ]
3362        }
3363      }
3364    ]
3365  },
3366  // 7. Idle Lateral Groove Sway (Slow gentle stage sway)
3367  {
3368    id: "idle_groove_sway",
3369    name: "Idle Stage Sway",
3370    category: "idle",
3371    trigger: "idle",
3372    priority: 3,
3373    idleSchedule: {
3374      minIntervalMs: 15e3,
3375      maxIntervalMs: 35e3,
3376      durationMs: 4e3,
3377      blendInMs: 600,
3378      blendOutMs: 600
3379    },
3380    channels: [
3381      {
3382        target: "bodySwayX",
3383        driver: "keyframe",
3384        keyframes: {
3385          frames: [
3386            { time: 0, value: 0 },
3387            { time: 0.25, value: 4, easing: "sine" },
3388            { time: 0.5, value: 0, easing: "sine" },
3389            { time: 0.75, value: -4, easing: "sine" },
3390            { time: 1, value: 0, easing: "sine" }
3391          ]
3392        }
3393      },
3394      {
3395        target: "headTilt",
3396        driver: "keyframe",
3397        keyframes: {
3398          frames: [
3399            { time: 0, value: 0 },
3400            { time: 0.25, value: -3, easing: "sine" },
3401            { time: 0.5, value: 0, easing: "sine" },
3402            { time: 0.75, value: 3, easing: "sine" },
3403            { time: 1, value: 0, easing: "sine" }
3404          ]
3405        }
3406      }
3407    ]
3408  },
3409  // 8. Idle Friendly Hand Wave (Casual paw wave to crowd/neighbor)
3410  {
3411    id: "idle_friendly_wave",
3412    name: "Idle Friendly Hand Wave",
3413    category: "idle",
3414    trigger: "idle",
3415    priority: 7,
3416    idleSchedule: {
3417      minIntervalMs: 16e3,
3418      maxIntervalMs: 4e4,
3419      durationMs: 2400,
3420      blendInMs: 350,
3421      blendOutMs: 350
3422    },
3423    channels: [
3424      {
3425        target: "targetR",
3426        driver: "keyframe",
3427        keyframes: {
3428          frames: [
3429            { time: 0, value: 0 },
3430            { time: 0.2, value: 1, easing: "sine" },
3431            { time: 0.8, value: 1, easing: "linear" },
3432            { time: 1, value: 0, easing: "sine" }
3433          ]
3434        }
3435      },
3436      {
3437        target: "headTilt",
3438        driver: "keyframe",
3439        keyframes: {
3440          frames: [
3441            { time: 0, value: 0 },
3442            { time: 0.2, value: -4, easing: "sine" },
3443            { time: 0.8, value: -4, easing: "linear" },
3444            { time: 1, value: 0, easing: "sine" }
3445          ]
3446        }
3447      },
3448      {
3449        target: "mouthOpen",
3450        driver: "keyframe",
3451        keyframes: {
3452          frames: [
3453            { time: 0, value: 0 },
3454            { time: 0.2, value: 0.35, easing: "sine" },
3455            { time: 0.8, value: 0.35, easing: "linear" },
3456            { time: 1, value: 0, easing: "sine" }
3457          ]
3458        }
3459      }
3460    ]
3461  },
3462  // 9. Idle Saliva Drip (Humorous dazed drool forming, stretching, and dripping from mouth)
3463  {
3464    id: "idle_saliva_drip",
3465    name: "Idle Saliva Drip",
3466    category: "idle",
3467    trigger: "idle",
3468    priority: 6,
3469    idleSchedule: {
3470      minIntervalMs: 12e3,
3471      maxIntervalMs: 3e4,
3472      durationMs: 3200,
3473      blendInMs: 300,
3474      blendOutMs: 300
3475    },
3476    channels: [
3477      {
3478        target: "headTilt",
3479        driver: "keyframe",
3480        keyframes: {
3481          frames: [
3482            { time: 0, value: 0 },
3483            { time: 0.25, value: 3.5, easing: "sine" },
3484            { time: 0.7, value: 4.5, easing: "linear" },
3485            { time: 0.9, value: 1.5, easing: "sine" },
3486            { time: 1, value: 0, easing: "sine" }
3487          ]
3488        }
3489      },
3490      {
3491        target: "mouthOpen",
3492        driver: "keyframe",
3493        keyframes: {
3494          frames: [
3495            { time: 0, value: 0 },
3496            { time: 0.15, value: 0.45, easing: "sine" },
3497            { time: 0.75, value: 0.5, easing: "linear" },
3498            { time: 0.92, value: 0.15, easing: "sine" },
3499            { time: 1, value: 0, easing: "sine" }
3500          ]
3501        }
3502      },
3503      {
3504        target: "salivaDrip",
3505        driver: "keyframe",
3506        keyframes: {
3507          frames: [
3508            { time: 0, value: 0 },
3509            { time: 1, value: 1, easing: "linear" }
3510          ]
3511        }
3512      }
3513    ]
3514  },
3515  // 10. Idle Flying Sweat (Humorous exhausted/nervous head shake flinging sweat droplets outward)
3516  {
3517    id: "idle_flying_sweat",
3518    name: "Idle Flying Sweat",
3519    category: "idle",
3520    trigger: "idle",
3521    priority: 6,
3522    idleSchedule: {
3523      minIntervalMs: 14e3,
3524      maxIntervalMs: 32e3,
3525      durationMs: 2400,
3526      blendInMs: 250,
3527      blendOutMs: 250
3528    },
3529    channels: [
3530      {
3531        target: "headTilt",
3532        driver: "keyframe",
3533        keyframes: {
3534          frames: [
3535            { time: 0, value: 0 },
3536            { time: 0.15, value: -3.5, easing: "sine" },
3537            { time: 0.3, value: 3.5, easing: "sine" },
3538            { time: 0.45, value: -2.5, easing: "sine" },
3539            { time: 0.6, value: 2, easing: "sine" },
3540            { time: 0.8, value: 0, easing: "sine" },
3541            { time: 1, value: 0, easing: "sine" }
3542          ]
3543        }
3544      },
3545      {
3546        target: "mouthOpen",
3547        driver: "keyframe",
3548        keyframes: {
3549          frames: [
3550            { time: 0, value: 0 },
3551            { time: 0.2, value: 0.35, easing: "sine" },
3552            { time: 0.7, value: 0.3, easing: "linear" },
3553            { time: 0.9, value: 0, easing: "sine" },
3554            { time: 1, value: 0, easing: "sine" }
3555          ]
3556        }
3557      },
3558      {
3559        target: "sweatFly",
3560        driver: "keyframe",
3561        keyframes: {
3562          frames: [
3563            { time: 0, value: 0 },
3564            { time: 0.15, value: 0 },
3565            { time: 0.85, value: 1, easing: "linear" },
3566            { time: 1, value: 1, easing: "linear" }
3567          ]
3568        }
3569      }
3570    ]
3571  }
3572];
3573function getActionsForInstrument(program, holdStyle, config) {
3574  if ((config == null ? void 0 : config.programActions) && config.programActions[program]) {
3575    return config.programActions[program];
3576  }
3577  if (PROGRAM_SPECIFIC_ACTIONS[program]) {
3578    return PROGRAM_SPECIFIC_ACTIONS[program];
3579  }
3580  if ((config == null ? void 0 : config.holdStyleActions) && config.holdStyleActions[holdStyle]) {
3581    return config.holdStyleActions[holdStyle];
3582  }
3583  return DEFAULT_HOLD_STYLE_ACTIONS[holdStyle] || DEFAULT_HOLD_STYLE_ACTIONS.standard;
3584}
3585function getIdleActionsList(config) {
3586  if ((config == null ? void 0 : config.idleActions) && config.idleActions.length > 0) {
3587    let list = [...config.idleActions];
3588    if (!list.some((a) => a.id === "idle_saliva_drip")) {
3589      const defaultSaliva = DEFAULT_IDLE_ACTIONS.find((a) => a.id === "idle_saliva_drip");
3590      if (defaultSaliva) list.push(defaultSaliva);
3591    }
3592    if (!list.some((a) => a.id === "idle_flying_sweat")) {
3593      const defaultSweat = DEFAULT_IDLE_ACTIONS.find((a) => a.id === "idle_flying_sweat");
3594      if (defaultSweat) list.push(defaultSweat);
3595    }
3596    return list;
3597  }
3598  return DEFAULT_IDLE_ACTIONS;
3599}
3600const ANIMAL_GEOMETRY = {
3601  fox: {
3602    head: { x: -55, y: -89, w: 110, h: 100 },
3603    body: { x: -93, y: -12, w: 186, h: 130 },
3604    shoulderL: { x: -37, y: 10 },
3605    shoulderR: { x: 37, y: 10 },
3606    upperL: { x: -9, y: -3, w: 18, h: 32, len: 22 },
3607    lowerL: { x: -8, y: -3, w: 16, h: 28, len: 20 },
3608    handL: { x: -8, y: -3, w: 16, h: 18 },
3609    upperR: { x: -9, y: -3, w: 18, h: 32, len: 22 },
3610    lowerR: { x: -8, y: -3, w: 16, h: 28, len: 20 },
3611    handR: { x: -8, y: -3, w: 16, h: 18 }
3612  },
3613  bear: {
3614    head: { x: -55, y: -94, w: 110, h: 96 },
3615    body: { x: -93, y: -12, w: 186, h: 130 },
3616    shoulderL: { x: -37, y: 10 },
3617    shoulderR: { x: 37, y: 10 },
3618    upperL: { x: -9, y: -3, w: 18, h: 32, len: 22 },
3619    lowerL: { x: -8, y: -3, w: 16, h: 28, len: 20 },
3620    handL: { x: -8, y: -3, w: 16, h: 18 },
3621    upperR: { x: -9, y: -3, w: 18, h: 32, len: 22 },
3622    lowerR: { x: -8, y: -3, w: 16, h: 28, len: 20 },
3623    handR: { x: -8, y: -3, w: 16, h: 18 }
3624  },
3625  wolf: {
3626    head: { x: -55, y: -105, w: 110, h: 128 },
3627    body: { x: -93, y: -12, w: 186, h: 130 },
3628    shoulderL: { x: -37, y: 10 },
3629    shoulderR: { x: 37, y: 10 },
3630    upperL: { x: -9, y: -3, w: 18, h: 32, len: 22 },
3631    lowerL: { x: -8, y: -3, w: 16, h: 28, len: 20 },
3632    handL: { x: -8, y: -3, w: 16, h: 18 },
3633    upperR: { x: -9, y: -3, w: 18, h: 32, len: 22 },
3634    lowerR: { x: -8, y: -3, w: 16, h: 28, len: 20 },
3635    handR: { x: -8, y: -3, w: 16, h: 18 }
3636  },
3637  lion: {
3638    head: { x: -58, y: -98, w: 116, h: 123 },
3639    body: { x: -93, y: -12, w: 186, h: 130 },
3640    shoulderL: { x: -37, y: 10 },
3641    shoulderR: { x: 37, y: 10 },
3642    upperL: { x: -9, y: -3, w: 18, h: 32, len: 22 },
3643    lowerL: { x: -8, y: -3, w: 16, h: 28, len: 20 },
3644    handL: { x: -8, y: -3, w: 16, h: 18 },
3645    upperR: { x: -9, y: -3, w: 18, h: 32, len: 22 },
3646    lowerR: { x: -8, y: -3, w: 16, h: 28, len: 20 },
3647    handR: { x: -8, y: -3, w: 16, h: 18 }
3648  },
3649  owl: {
3650    head: { x: -55, y: -92, w: 110, h: 105 },
3651    body: { x: -93, y: -12, w: 186, h: 130 },
3652    shoulderL: { x: -37, y: 10 },
3653    shoulderR: { x: 37, y: 10 },
3654    upperL: { x: -9, y: -3, w: 18, h: 32, len: 22 },
3655    lowerL: { x: -8, y: -3, w: 16, h: 28, len: 20 },
3656    handL: { x: -8, y: -3, w: 16, h: 18 },
3657    upperR: { x: -9, y: -3, w: 18, h: 32, len: 22 },
3658    lowerR: { x: -8, y: -3, w: 16, h: 28, len: 20 },
3659    handR: { x: -8, y: -3, w: 16, h: 18 }
3660  },
3661  raccoon: {
3662    head: { x: -57, y: -90, w: 114, h: 89 },
3663    body: { x: -93, y: -12, w: 186, h: 130 },
3664    shoulderL: { x: -37, y: 10 },
3665    shoulderR: { x: 37, y: 10 },
3666    upperL: { x: -9, y: -3, w: 18, h: 32, len: 22 },
3667    lowerL: { x: -8, y: -3, w: 16, h: 28, len: 20 },
3668    handL: { x: -8, y: -3, w: 16, h: 18 },
3669    upperR: { x: -9, y: -3, w: 18, h: 32, len: 22 },
3670    lowerR: { x: -8, y: -3, w: 16, h: 28, len: 20 },
3671    handR: { x: -8, y: -3, w: 16, h: 18 }
3672  },
3673  hamster: {
3674    head: { x: -55, y: -80, w: 110, h: 96 },
3675    body: { x: -52, y: -6, w: 104, h: 74 },
3676    shoulderL: { x: -28, y: 8 },
3677    shoulderR: { x: 28, y: 8 },
3678    upperL: { x: -7, y: -2, w: 14, h: 22, len: 14 },
3679    lowerL: { x: -6, y: -2, w: 12, h: 18, len: 12 },
3680    handL: { x: -7, y: -2.5, w: 14, h: 16 },
3681    upperR: { x: -7, y: -2, w: 14, h: 22, len: 14 },
3682    lowerR: { x: -6, y: -2, w: 12, h: 18, len: 12 },
3683    handR: { x: -7, y: -2.5, w: 14, h: 16 }
3684  },
3685  otter: {
3686    head: { x: -55, y: -64, w: 110, h: 71 },
3687    body: { x: -38, y: -4, w: 76, h: 86 },
3688    shoulderL: { x: -22, y: 10 },
3689    shoulderR: { x: 22, y: 10 },
3690    upperL: { x: -7, y: -2, w: 14, h: 22, len: 15 },
3691    lowerL: { x: -6, y: -2, w: 12, h: 18, len: 13 },
3692    handL: { x: -7, y: -2.5, w: 14, h: 16 },
3693    upperR: { x: -7, y: -2, w: 14, h: 22, len: 15 },
3694    lowerR: { x: -6, y: -2, w: 12, h: 18, len: 13 },
3695    handR: { x: -7, y: -2.5, w: 14, h: 16 }
3696  },
3697  deer: {
3698    head: { x: -55, y: -78, w: 110, h: 87 },
3699    body: { x: -28, y: -4, w: 56, h: 88 },
3700    shoulderL: { x: -18, y: 10 },
3701    shoulderR: { x: 18, y: 10 },
3702    upperL: { x: -7, y: -2, w: 14, h: 22, len: 15 },
3703    lowerL: { x: -6, y: -2, w: 12, h: 18, len: 13 },
3704    handL: { x: -7, y: -2.5, w: 14, h: 16 },
3705    upperR: { x: -7, y: -2, w: 14, h: 22, len: 15 },
3706    lowerR: { x: -6, y: -2, w: 12, h: 18, len: 13 },
3707    handR: { x: -7, y: -2.5, w: 14, h: 16 }
3708  },
3709  penguin: {
3710    head: { x: -55, y: -72, w: 110, h: 81 },
3711    body: { x: -42, y: -4, w: 84, h: 86 },
3712    shoulderL: { x: -24, y: 10 },
3713    shoulderR: { x: 24, y: 10 },
3714    upperL: { x: -7, y: -2, w: 14, h: 22, len: 15 },
3715    lowerL: { x: -6, y: -2, w: 12, h: 18, len: 13 },
3716    handL: { x: -7, y: -2.5, w: 14, h: 16 },
3717    upperR: { x: -7, y: -2, w: 14, h: 22, len: 15 },
3718    lowerR: { x: -6, y: -2, w: 12, h: 18, len: 13 },
3719    handR: { x: -7, y: -2.5, w: 14, h: 16 }
3720  },
3721  badger: {
3722    head: { x: -55, y: -65, w: 110, h: 72 },
3723    body: { x: -44, y: -4, w: 88, h: 86 },
3724    shoulderL: { x: -25, y: 10 },
3725    shoulderR: { x: 25, y: 10 },
3726    upperL: { x: -7, y: -2, w: 14, h: 22, len: 15 },
3727    lowerL: { x: -6, y: -2, w: 12, h: 18, len: 13 },
3728    handL: { x: -7, y: -2.5, w: 14, h: 16 },
3729    upperR: { x: -7, y: -2, w: 14, h: 22, len: 15 },
3730    lowerR: { x: -6, y: -2, w: 12, h: 18, len: 13 },
3731    handR: { x: -7, y: -2.5, w: 14, h: 16 }
3732  },
3733  rabbit: {
3734    head: { x: -55, y: -105, w: 110, h: 115 },
3735    body: { x: -45, y: -4, w: 90, h: 86 },
3736    shoulderL: { x: -25, y: 10 },
3737    shoulderR: { x: 25, y: 10 },
3738    upperL: { x: -7, y: -2, w: 14, h: 22, len: 15 },
3739    lowerL: { x: -6, y: -2, w: 12, h: 18, len: 13 },
3740    handL: { x: -7, y: -2.5, w: 14, h: 16 },
3741    upperR: { x: -7, y: -2, w: 14, h: 22, len: 15 },
3742    lowerR: { x: -6, y: -2, w: 12, h: 18, len: 13 },
3743    handR: { x: -7, y: -2.5, w: 14, h: 16 }
3744  },
3745  koala: {
3746    head: { x: -55, y: -56, w: 110, h: 63 },
3747    body: { x: -46, y: -4, w: 92, h: 86 },
3748    shoulderL: { x: -26, y: 10 },
3749    shoulderR: { x: 26, y: 10 },
3750    upperL: { x: -7, y: -2, w: 14, h: 22, len: 15 },
3751    lowerL: { x: -6, y: -2, w: 12, h: 18, len: 13 },
3752    handL: { x: -7, y: -2.5, w: 14, h: 16 },
3753    upperR: { x: -7, y: -2, w: 14, h: 22, len: 15 },
3754    lowerR: { x: -6, y: -2, w: 12, h: 18, len: 13 },
3755    handR: { x: -7, y: -2.5, w: 14, h: 16 }
3756  },
3757  bat: {
3758    head: { x: -55, y: -84, w: 110, h: 92 },
3759    body: { x: -42, y: -4, w: 84, h: 86 },
3760    shoulderL: { x: -24, y: 10 },
3761    shoulderR: { x: 24, y: 10 },
3762    upperL: { x: -7, y: -2, w: 14, h: 22, len: 15 },
3763    lowerL: { x: -6, y: -2, w: 12, h: 18, len: 13 },
3764    handL: { x: -7, y: -2.5, w: 14, h: 16 },
3765    upperR: { x: -7, y: -2, w: 14, h: 22, len: 15 },
3766    lowerR: { x: -6, y: -2, w: 12, h: 18, len: 13 },
3767    handR: { x: -7, y: -2.5, w: 14, h: 16 }
3768  },
3769  llama: {
3770    head: { x: -45, y: -130, w: 90, h: 140 },
3771    body: { x: -42, y: -4, w: 84, h: 86 },
3772    shoulderL: { x: -24, y: 10 },
3773    shoulderR: { x: 24, y: 10 },
3774    upperL: { x: -7, y: -2, w: 14, h: 22, len: 15 },
3775    lowerL: { x: -6, y: -2, w: 12, h: 18, len: 13 },
3776    handL: { x: -7, y: -2.5, w: 14, h: 16 },
3777    upperR: { x: -7, y: -2, w: 14, h: 22, len: 15 },
3778    lowerR: { x: -6, y: -2, w: 12, h: 18, len: 13 },
3779    handR: { x: -7, y: -2.5, w: 14, h: 16 }
3780  },
3781  gorilla: {
3782    head: { x: -55, y: -72, w: 110, h: 81 },
3783    body: { x: -44, y: -4, w: 88, h: 86 },
3784    shoulderL: { x: -25, y: 10 },
3785    shoulderR: { x: 25, y: 10 },
3786    upperL: { x: -7, y: -2, w: 14, h: 22, len: 15 },
3787    lowerL: { x: -6, y: -2, w: 12, h: 18, len: 13 },
3788    handL: { x: -7, y: -2.5, w: 14, h: 16 },
3789    upperR: { x: -7, y: -2, w: 14, h: 22, len: 15 },
3790    lowerR: { x: -6, y: -2, w: 12, h: 18, len: 13 },
3791    handR: { x: -7, y: -2.5, w: 14, h: 16 }
3792  },
3793  parrot: {
3794    head: { x: -55, y: -70, w: 110, h: 79 },
3795    body: { x: -40, y: -4, w: 80, h: 86 },
3796    shoulderL: { x: -22, y: 10 },
3797    shoulderR: { x: 22, y: 10 },
3798    upperL: { x: -7, y: -2, w: 14, h: 22, len: 15 },
3799    lowerL: { x: -6, y: -2, w: 12, h: 18, len: 13 },
3800    handL: { x: -7, y: -2.5, w: 14, h: 16 },
3801    upperR: { x: -7, y: -2, w: 14, h: 22, len: 15 },
3802    lowerR: { x: -6, y: -2, w: 12, h: 18, len: 13 },
3803    handR: { x: -7, y: -2.5, w: 14, h: 16 }
3804  }
3805};
3806const ANIMAL_MOUTH_OFFSETS = {
3807  fox: { xRatio: 0.04, yRatio: 0.8 },
3808  bear: { xRatio: 0.03, yRatio: 0.82 },
3809  wolf: { xRatio: 0.04, yRatio: 0.76 },
3810  lion: { xRatio: 0.04, yRatio: 0.78 },
3811  owl: { xRatio: 0, yRatio: 0.82 },
3812  raccoon: { xRatio: 0.03, yRatio: 0.82 },
3813  hamster: { xRatio: 0.03, yRatio: 0.82 },
3814  otter: { xRatio: 0.03, yRatio: 0.81 },
3815  deer: { xRatio: 0.03, yRatio: 0.84 },
3816  penguin: { xRatio: 0, yRatio: 0.82 },
3817  badger: { xRatio: 0.03, yRatio: 0.82 },
3818  rabbit: { xRatio: 0.03, yRatio: 0.84 },
3819  koala: { xRatio: 0.03, yRatio: 0.86 },
3820  bat: { xRatio: 0.03, yRatio: 0.8 },
3821  llama: { xRatio: 0.03, yRatio: 0.82 },
3822  gorilla: { xRatio: 0.03, yRatio: 0.84 },
3823  parrot: { xRatio: 0.02, yRatio: 0.82 }
3824};
3825function renderSalivaDrip(progress, mx, my) {
3826  if (progress <= 5e-3 || progress >= 1) return null;
3827  const fluidFill = "#7dd3fc";
3828  const fluidStroke = "#0284c7";
3829  if (progress < 0.35) {
3830    const u2 = progress / 0.35;
3831    const beadR = Math.max(0.6, 3.2 * Math.sin(u2 * Math.PI * 0.5));
3832    const wobbleX = Math.sin(u2 * 14) * 0.5;
3833    const cx = mx + wobbleX;
3834    const cy = my + beadR * 0.75;
3835    return /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { id: "saliva-drip-bead", opacity: Math.min(1, u2 * 2.5), children: [
3836      /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx, cy, rx: beadR, ry: beadR * 1.15, fill: fluidFill, fillOpacity: 0.88, stroke: fluidStroke, strokeWidth: 1 }),
3837      /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: cx - beadR * 0.3, cy: cy - beadR * 0.35, r: Math.max(0.4, beadR * 0.28), fill: "#ffffff", fillOpacity: 0.95 })
3838    ] });
3839  }
3840  if (progress < 0.72) {
3841    const u2 = (progress - 0.35) / (0.72 - 0.35);
3842    const stretchDist = 4 + Math.pow(u2, 1.3) * 26;
3843    const bulbR = 3 + u2 * 0.6;
3844    const bulbX = mx + Math.sin(u2 * 8) * 0.8;
3845    const bulbY = my + stretchDist;
3846    const wTop = 2 * (1 - 0.35 * u2);
3847    const wMid = Math.max(0.6, 1.2 * (1 - 0.55 * u2));
3848    const midY = my + stretchDist * 0.45;
3849    const d = `M ${mx - wTop} ${my} Q ${mx - wMid} ${midY}, ${bulbX - bulbR} ${bulbY} A ${bulbR} ${bulbR * 1.15} 0 1 0 ${bulbX + bulbR} ${bulbY} Q ${mx + wMid} ${midY}, ${mx + wTop} ${my} Z`;
3850    return /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { id: "saliva-drip-stretch", children: [
3851      /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d, fill: fluidFill, fillOpacity: 0.88, stroke: fluidStroke, strokeWidth: 1, strokeLinejoin: "round" }),
3852      /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: bulbX - bulbR * 0.3, cy: bulbY - bulbR * 0.3, rx: bulbR * 0.32, ry: bulbR * 0.22, fill: "#ffffff", fillOpacity: 0.95 }),
3853      u2 > 0.2 && /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: mx, y1: my + 2, x2: bulbX, y2: my + stretchDist * 0.65, stroke: "#ffffff", strokeWidth: 0.7, strokeOpacity: 0.65, strokeLinecap: "round" })
3854    ] });
3855  }
3856  if (progress < 0.94) {
3857    const u2 = (progress - 0.72) / (0.94 - 0.72);
3858    const topR = Math.max(0, 1.6 * (1 - u2 * 2));
3859    const startY = my + 30;
3860    const fallDist = u2 * 20 + Math.pow(u2, 2) * 32;
3861    const dropY = startY + fallDist;
3862    const dropR = 2.5;
3863    const dropOpacity = Math.max(0, 1 - Math.pow(u2, 3));
3864    return /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { id: "saliva-drip-freefall", children: [
3865      topR > 0.2 && /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: mx, cy: my + topR, rx: topR, ry: topR * 0.8, fill: fluidFill, fillOpacity: 0.7, stroke: fluidStroke, strokeWidth: 0.8 }),
3866      dropOpacity > 0.05 && /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { opacity: dropOpacity, children: [
3867        /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: mx, cy: dropY, rx: dropR * 0.85, ry: dropR * 1.35, fill: fluidFill, fillOpacity: 0.88, stroke: fluidStroke, strokeWidth: 1 }),
3868        /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: mx - dropR * 0.25, cy: dropY - dropR * 0.4, r: dropR * 0.28, fill: "#ffffff", fillOpacity: 0.95 })
3869      ] })
3870    ] });
3871  }
3872  const u = (progress - 0.94) / 0.06;
3873  const impactY = my + 82;
3874  const splashOpacity = Math.max(0, 1 - u);
3875  const spX1 = mx - 2.5 * u;
3876  const spY1 = impactY - 2 * u + 3.5 * u * u;
3877  const spX2 = mx + 3.2 * u;
3878  const spY2 = impactY - 2.5 * u + 4 * u * u;
3879  return /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { id: "saliva-drip-splash", opacity: splashOpacity, children: [
3880    /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: spX1, cy: spY1, r: 0.9, fill: fluidFill, stroke: fluidStroke, strokeWidth: 0.5 }),
3881    /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: spX2, cy: spY2, r: 1.1, fill: fluidFill, stroke: fluidStroke, strokeWidth: 0.5 })
3882  ] });
3883}
3884function renderFlyingSweat(progress, headCenterX, browY, headW, headH) {
3885  if (progress <= 5e-3 || progress >= 1) return null;
3886  const fluidFill = "#38bdf8";
3887  const fluidStroke = "#0284c7";
3888  const droplets = [
3889    { side: -1, angleDeg: -130, speed: 38, baseR: 2.6, delay: 0 },
3890    { side: -1, angleDeg: -155, speed: 46, baseR: 3.2, delay: 0.06 },
3891    { side: -1, angleDeg: -175, speed: 32, baseR: 2.1, delay: 0.12 },
3892    { side: 1, angleDeg: -50, speed: 38, baseR: 2.6, delay: 0 },
3893    { side: 1, angleDeg: -25, speed: 46, baseR: 3.2, delay: 0.06 },
3894    { side: 1, angleDeg: -5, speed: 32, baseR: 2.1, delay: 0.12 }
3895  ];
3896  const templeOffset = headW * 0.28;
3897  const gravity = 34;
3898  return /* @__PURE__ */ jsxRuntimeExports.jsx("g", { id: "character-flying-sweat", pointerEvents: "none", children: droplets.map((d, i) => {
3899    if (progress < d.delay) return null;
3900    const localProgress = Math.min(1, (progress - d.delay) / (1 - d.delay));
3901    if (localProgress <= 0 || localProgress >= 1) return null;
3902    const angleRad = d.angleDeg * Math.PI / 180;
3903    const originX = headCenterX + d.side * templeOffset;
3904    const originY = browY;
3905    const vx = Math.cos(angleRad) * d.speed;
3906    const vy = Math.sin(angleRad) * d.speed;
3907    const x = originX + vx * localProgress;
3908    const y = originY + vy * localProgress + 0.5 * gravity * Math.pow(localProgress, 1.8);
3909    const curVx = vx;
3910    const curVy = vy + gravity * Math.pow(localProgress, 0.8);
3911    const flightAngleDeg = Math.atan2(curVy, curVx) * 180 / Math.PI;
3912    const elongation = 1 + Math.sin(localProgress * Math.PI) * 0.8;
3913    const rx = d.baseR * (1 - localProgress * 0.3);
3914    const ry = d.baseR * elongation * (1 - localProgress * 0.2);
3915    let opacity = 1;
3916    if (localProgress < 0.15) {
3917      opacity = localProgress / 0.15;
3918    } else if (localProgress > 0.65) {
3919      opacity = Math.max(0, (1 - localProgress) / 0.35);
3920    }
3921    return /* @__PURE__ */ jsxRuntimeExports.jsxs(
3922      "g",
3923      {
3924        transform: `translate(${x.toFixed(2)}, ${y.toFixed(2)}) rotate(${(flightAngleDeg - 90).toFixed(1)})`,
3925        opacity,
3926        children: [
3927          /* @__PURE__ */ jsxRuntimeExports.jsx(
3928            "path",
3929            {
3930              d: `M 0,${-ry.toFixed(2)} C ${(rx * 0.8).toFixed(2)},${(-ry * 0.5).toFixed(2)} ${rx.toFixed(2)},${(ry * 0.3).toFixed(2)} 0,${ry.toFixed(2)} C ${(-rx).toFixed(2)},${(ry * 0.3).toFixed(2)} ${(-rx * 0.8).toFixed(2)},${(-ry * 0.5).toFixed(2)} 0,${-ry.toFixed(2)} Z`,
3931              fill: fluidFill,
3932              fillOpacity: 0.88,
3933              stroke: fluidStroke,
3934              strokeWidth: 1
3935            }
3936          ),
3937          /* @__PURE__ */ jsxRuntimeExports.jsx(
3938            "circle",
3939            {
3940              cx: (-rx * 0.25).toFixed(2),
3941              cy: (-ry * 0.35).toFixed(2),
3942              r: Math.max(0.4, rx * 0.35).toFixed(2),
3943              fill: "#ffffff",
3944              fillOpacity: 0.92
3945            }
3946          )
3947        ]
3948      },
3949      i
3950    );
3951  }) });
3952}
3953function generateArmSleevePath(len1, len2, relElbowDeg, rShoulder = 7.5, rElbow = 7, rWrist = 6) {
3954  const alpha = relElbowDeg * Math.PI / 180;
3955  const eX = 0, eY = len1;
3956  const uX = -Math.sin(alpha);
3957  const uY = Math.cos(alpha);
3958  const nX = Math.cos(alpha);
3959  const nY = Math.sin(alpha);
3960  const wX = eX + len2 * uX;
3961  const wY = eY + len2 * uY;
3962  const wrX = wX + rWrist * nX;
3963  const wrY = wY + rWrist * nY;
3964  const wlX = wX - rWrist * nX;
3965  const wlY = wY - rWrist * nY;
3966  const srX = rShoulder;
3967  const srY = 0;
3968  const slX = -rShoulder;
3969  const slY = 0;
3970  let pathD = `M ${slX.toFixed(2)} ${slY.toFixed(2)} `;
3971  pathD += `A ${rShoulder} ${rShoulder} 0 0 1 ${srX.toFixed(2)} ${srY.toFixed(2)} `;
3972  if (Math.abs(relElbowDeg) < 3) {
3973    pathD += `L ${wrX.toFixed(2)} ${wrY.toFixed(2)} `;
3974    pathD += `L ${wlX.toFixed(2)} ${wlY.toFixed(2)} `;
3975    pathD += `Z`;
3976  } else if (relElbowDeg > 0) {
3977    pathD += `L ${rElbow.toFixed(2)} ${len1.toFixed(2)} `;
3978    const outerTargetX = eX + rElbow * nX;
3979    const outerTargetY = eY + rElbow * nY;
3980    pathD += `A ${rElbow} ${rElbow} 0 0 1 ${outerTargetX.toFixed(2)} ${outerTargetY.toFixed(2)} `;
3981    pathD += `L ${wrX.toFixed(2)} ${wrY.toFixed(2)} `;
3982    pathD += `L ${wlX.toFixed(2)} ${wlY.toFixed(2)} `;
3983    const tanHalf = Math.tan(alpha / 2);
3984    const creaseY = Math.max(len1 * 0.3, len1 - rElbow * tanHalf);
3985    const creaseX = -rElbow;
3986    pathD += `L ${creaseX.toFixed(2)} ${creaseY.toFixed(2)} `;
3987    pathD += `Z`;
3988  } else {
3989    const absAlpha = Math.abs(alpha);
3990    const tanHalf = Math.tan(absAlpha / 2);
3991    const creaseY = Math.max(len1 * 0.3, len1 - rElbow * tanHalf);
3992    const creaseX = rElbow;
3993    pathD += `L ${creaseX.toFixed(2)} ${creaseY.toFixed(2)} `;
3994    pathD += `L ${wrX.toFixed(2)} ${wrY.toFixed(2)} `;
3995    pathD += `L ${wlX.toFixed(2)} ${wlY.toFixed(2)} `;
3996    const outerTargetX = eX - rElbow * nX;
3997    const outerTargetY = eY - rElbow * nY;
3998    pathD += `L ${outerTargetX.toFixed(2)} ${outerTargetY.toFixed(2)} `;
3999    pathD += `A ${rElbow} ${rElbow} 0 0 1 ${(-rElbow).toFixed(2)} ${len1.toFixed(2)} `;
4000    pathD += `Z`;
4001  }
4002  return pathD;
4003}
4004function generateUpperArmSleevePath(len1, rShoulder = 7.5, rElbow = 7) {
4005  return `M ${-rShoulder} 0 A ${rShoulder} ${rShoulder} 0 0 1 ${rShoulder} 0 L ${rElbow} ${len1} A ${rElbow} ${rElbow} 0 0 1 ${-rElbow} ${len1} Z`;
4006}
4007function generateLowerArmSleevePath(len2, rElbow = 7, rWrist = 6) {
4008  return `M ${-rElbow} 0 A ${rElbow} ${rElbow} 0 0 1 ${rElbow} 0 L ${rWrist} ${len2} A ${rWrist} ${rWrist} 0 0 1 ${-rWrist} ${len2} Z`;
4009}
4010function getInstrumentCalibratedBasePose(instrument, hold) {
4011  var _a, _b, _c;
4012  const defaultBase = NATURAL_PERFORMANCE_POSES[hold] || NATURAL_PERFORMANCE_POSES.standard;
4013  const base = {
4014    targetL: { ...defaultBase.targetL },
4015    targetR: { ...defaultBase.targetR },
4016    bendL: defaultBase.bendL,
4017    bendR: defaultBase.bendR,
4018    wristTiltL: defaultBase.wristTiltL,
4019    wristTiltR: defaultBase.wristTiltR,
4020    bodyBounceFactor: defaultBase.bodyBounceFactor,
4021    bodySquashFactor: defaultBase.bodySquashFactor,
4022    hasBow: defaultBase.hasBow,
4023    hasSticks: defaultBase.hasSticks,
4024    instrumentRotation: defaultBase.instrumentRotation,
4025    contactPoint: defaultBase.contactPoint,
4026    fretTrack: defaultBase.fretTrack,
4027    slideTrack: defaultBase.slideTrack
4028  };
4029  const isMallet = ((_a = instrument.jointDef) == null ? void 0 : _a.jointType) === "mallet_bars";
4030  const isTubularBells = instrument.program === 14;
4031  const hasSticks = !!base.hasSticks || isMallet || ((_b = instrument.jointDef) == null ? void 0 : _b.jointType) === "drum_sticks";
4032  const isKeyboard = ((_c = instrument.jointDef) == null ? void 0 : _c.jointType) === "seated_keys" || hold === "sitting" && !hasSticks && !isMallet || instrument.program === 8;
4033  if (isMallet && hold === "sitting") {
4034    if (isTubularBells) {
4035      base.targetL = { x: -28, y: 38 };
4036      base.targetR = { x: 18, y: 6 };
4037      base.wristTiltL = -4;
4038      base.wristTiltR = -6;
4039    } else {
4040      base.targetL = { x: -14, y: 31 };
4041      base.targetR = { x: 14, y: 31 };
4042      base.wristTiltL = 0;
4043      base.wristTiltR = 0;
4044    }
4045  } else if (isKeyboard && hold === "sitting") {
4046    base.targetL = { x: -18, y: 38 };
4047    base.targetR = { x: 18, y: 38 };
4048    base.bendL = 1;
4049    base.bendR = -1;
4050    base.wristTiltL = -3;
4051    base.wristTiltR = 3;
4052  } else if (instrument.program === 46 || hold === "harp") {
4053    base.targetL = { x: -16, y: 38 };
4054    base.targetR = { x: 12, y: 30 };
4055    base.wristTiltL = 0;
4056    base.wristTiltR = 8;
4057  } else if (hold === "violin" || hold === "pizzicato") {
4058    base.targetL = hold === "pizzicato" ? { x: 4, y: 24 } : { x: -16, y: 32 };
4059    base.targetR = { x: 62, y: 4 };
4060    base.wristTiltL = hold === "pizzicato" ? 14 : -10;
4061    base.wristTiltR = -15;
4062  } else if (instrument.program === 56 || instrument.program === 59 || instrument.program === 61) {
4063    base.targetL = { x: 23, y: 7 };
4064    base.targetR = { x: 25, y: -9 };
4065    base.wristTiltL = 8;
4066    base.wristTiltR = -6;
4067  } else if (instrument.program === 57) {
4068    base.targetL = { x: 0, y: -4 };
4069    base.targetR = { x: 32, y: 3 };
4070    base.wristTiltL = -4;
4071    base.wristTiltR = 8;
4072  } else if (instrument.program === 58) {
4073    base.targetL = { x: -22, y: 50 };
4074    base.targetR = { x: 15, y: 16 };
4075    base.wristTiltL = 12;
4076    base.wristTiltR = -4;
4077  } else if (instrument.program === 60) {
4078    base.targetL = { x: -7, y: 24 };
4079    base.targetR = { x: 28, y: 56 };
4080    base.wristTiltL = 6;
4081    base.wristTiltR = -15;
4082  } else if (instrument.program === 64 || instrument.program === 68 || instrument.program === 71 || instrument.program === 111) {
4083    base.targetL = { x: 12, y: 27 };
4084    base.targetR = { x: 24, y: 64 };
4085    base.wristTiltL = 8;
4086    base.wristTiltR = -6;
4087  } else if (instrument.program === 65) {
4088    base.targetL = { x: 15, y: 31 };
4089    base.targetR = { x: 22, y: 70 };
4090    base.wristTiltL = 8;
4091    base.wristTiltR = -6;
4092  } else if (instrument.program === 66) {
4093    base.targetL = { x: 15, y: 35 };
4094    base.targetR = { x: 23, y: 76 };
4095    base.wristTiltL = 8;
4096    base.wristTiltR = -6;
4097  } else if (instrument.program === 67) {
4098    base.targetL = { x: 16, y: 64 };
4099    base.targetR = { x: 18, y: 34 };
4100    base.wristTiltL = -6;
4101    base.wristTiltR = 8;
4102  } else if (instrument.program === 72) {
4103    base.targetL = { x: -30, y: 2 };
4104    base.targetR = { x: 6, y: -2 };
4105    base.bendL = -1;
4106    base.bendR = 1;
4107    base.wristTiltL = -10;
4108    base.wristTiltR = 10;
4109  } else if (instrument.program === 73) {
4110    base.targetL = { x: -44, y: 2 };
4111    base.targetR = { x: 6, y: -2 };
4112    base.bendL = -1;
4113    base.bendR = 1;
4114    base.wristTiltL = -10;
4115    base.wristTiltR = 10;
4116  } else if (instrument.program === 74 || instrument.program === 78) {
4117    base.targetL = { x: 10, y: 18 };
4118    base.targetR = { x: 18, y: 42 };
4119    base.wristTiltL = 8;
4120    base.wristTiltR = -6;
4121  } else if (instrument.program === 75) {
4122    base.targetL = { x: -16, y: 4 };
4123    base.targetR = { x: 16, y: 6 };
4124    base.wristTiltL = -10;
4125    base.wristTiltR = 10;
4126  } else if (instrument.program === 76) {
4127    base.targetL = { x: -14, y: 18 };
4128    base.targetR = { x: 14, y: 18 };
4129    base.wristTiltL = -8;
4130    base.wristTiltR = 8;
4131  } else if (instrument.program === 77) {
4132    base.targetL = { x: 10, y: 20 };
4133    base.targetR = { x: 18, y: 44 };
4134    base.wristTiltL = 8;
4135    base.wristTiltR = -6;
4136  } else if (instrument.program === 79) {
4137    base.targetL = { x: -18, y: 6 };
4138    base.targetR = { x: 18, y: 6 };
4139    base.wristTiltL = -12;
4140    base.wristTiltR = 12;
4141  } else if (instrument.program === 69) {
4142    base.targetL = { x: 15, y: 33 };
4143    base.targetR = { x: 25, y: 70 };
4144    base.wristTiltL = 8;
4145    base.wristTiltR = -6;
4146  } else if (instrument.program === 70) {
4147    base.targetL = { x: -12, y: 35 };
4148    base.targetR = { x: 17, y: 95 };
4149    base.wristTiltL = 10;
4150    base.wristTiltR = -8;
4151  } else if (instrument.program === 52) {
4152    base.targetL = { x: -14, y: 22 };
4153    base.targetR = { x: 14, y: 22 };
4154    base.wristTiltL = -8;
4155    base.wristTiltR = 8;
4156  } else if (instrument.program === 53) {
4157    base.targetL = { x: -6, y: 26 };
4158    base.targetR = { x: 22, y: 16 };
4159    base.wristTiltL = -6;
4160    base.wristTiltR = 14;
4161  } else if (instrument.program === 55) {
4162    base.targetL = { x: -24, y: 20 };
4163    base.targetR = { x: 24, y: -6 };
4164    base.wristTiltL = -14;
4165    base.wristTiltR = 15;
4166  } else if (instrument.program === 47) {
4167    base.targetL = { x: -20, y: 32 };
4168    base.targetR = { x: 20, y: 32 };
4169    base.wristTiltL = -4;
4170    base.wristTiltR = 4;
4171  } else if (instrument.program === 10) {
4172    base.targetL = { x: -14, y: 34 };
4173    base.targetR = { x: 20, y: 34 };
4174    base.wristTiltL = -4;
4175    base.wristTiltR = 8;
4176  } else if (instrument.program === 108) {
4177    base.targetL = { x: -22, y: 42 };
4178    base.targetR = { x: 22, y: 42 };
4179    base.wristTiltL = -8;
4180    base.wristTiltR = 8;
4181  } else if (instrument.program === 112) {
4182    base.targetL = { x: -20, y: 34 };
4183    base.targetR = { x: 16, y: 22 };
4184    base.wristTiltL = -6;
4185    base.wristTiltR = 8;
4186  } else if (instrument.program === 113) {
4187    base.targetL = { x: -16, y: 24 };
4188    base.targetR = { x: 20, y: 28 };
4189    base.wristTiltL = -10;
4190    base.wristTiltR = 12;
4191  } else if (instrument.program === 115) {
4192    base.targetL = { x: -16, y: 34 };
4193    base.targetR = { x: 18, y: 26 };
4194    base.wristTiltL = -8;
4195    base.wristTiltR = 10;
4196  } else if (instrument.program >= 114 && instrument.program <= 119) {
4197    base.targetL = { x: -22, y: 36 };
4198    base.targetR = { x: 22, y: 36 };
4199    base.wristTiltL = -6;
4200    base.wristTiltR = 6;
4201  } else if (instrument.program === 121) {
4202    base.targetL = { x: -16, y: 22 };
4203    base.targetR = { x: 18, y: 20 };
4204    base.wristTiltL = -6;
4205    base.wristTiltR = 8;
4206  } else if (instrument.program === 122) {
4207    base.targetL = { x: -24, y: 36 };
4208    base.targetR = { x: 24, y: 36 };
4209    base.wristTiltL = -8;
4210    base.wristTiltR = 8;
4211  } else if (instrument.program === 123) {
4212    base.targetL = { x: -14, y: 16 };
4213    base.targetR = { x: 16, y: 16 };
4214    base.wristTiltL = -8;
4215    base.wristTiltR = 8;
4216  } else if (instrument.program === 124) {
4217    base.targetL = { x: -16, y: 36 };
4218    base.targetR = { x: 22, y: 20 };
4219    base.wristTiltL = -6;
4220    base.wristTiltR = 12;
4221  } else if (instrument.program === 125) {
4222    base.targetL = { x: -16, y: 34 };
4223    base.targetR = { x: 18, y: 34 };
4224    base.wristTiltL = -8;
4225    base.wristTiltR = 8;
4226  } else if (instrument.program === 126) {
4227    base.targetL = { x: -22, y: 32 };
4228    base.targetR = { x: 22, y: 32 };
4229    base.wristTiltL = -10;
4230    base.wristTiltR = 10;
4231  } else if (instrument.program === 127) {
4232    base.targetL = { x: -18, y: 36 };
4233    base.targetR = { x: 20, y: 22 };
4234    base.wristTiltL = -6;
4235    base.wristTiltR = 10;
4236  } else if (hold === "harmonica") {
4237    base.targetL = { x: -12, y: 3 };
4238    base.targetR = { x: 12, y: 3 };
4239    base.wristTiltL = 18;
4240    base.wristTiltR = -18;
4241  } else if (hold === "accordion") {
4242    base.targetL = { x: -30, y: 34 };
4243    base.targetR = { x: 30, y: 34 };
4244    base.wristTiltL = -12;
4245    base.wristTiltR = 12;
4246  }
4247  return base;
4248}
4249const SouthparkCharacterComponent = ({
4250  animal,
4251  instrument,
4252  isActive,
4253  velocity,
4254  activePitch,
4255  currentTime,
4256  bpm,
4257  bps,
4258  isPlaying,
4259  physicsMode,
4260  isSoloed,
4261  isMuted,
4262  isHidden,
4263  isHovered,
4264  isSelected,
4265  scale = 1,
4266  onClick,
4267  onContextMenu,
4268  onHover,
4269  customConfig,
4270  showInstrument = true,
4271  showSkeleton = false,
4272  skeletonOnly = false,
4273  ghostSilhouette = false,
4274  manualLimbAngles,
4275  renderArmsOnly = false,
4276  hideArms = false,
4277  costumes,
4278  customCostumesMap,
4279  actionsConfig,
4280  musicalFeatures,
4281  variant,
4282  variantFilterId
4283}) => {
4284  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r;
4285  const restPosture = (musicalFeatures == null ? void 0 : musicalFeatures.restPosture) ?? (isActive ? "playing" : "relaxed_rest");
4286  const idleScheduleRef = reactExports.useRef({
4287    currentAction: null,
4288    startTime: 0,
4289    duration: 0,
4290    blendIn: 400,
4291    blendOut: 400,
4292    nextTriggerTime: (typeof performance !== "undefined" ? performance.now() : Date.now()) + 2e3 + Math.random() * 5e3
4293  });
4294  const [, setWaveAnimTick] = reactExports.useState(0);
4295  const hoverWaveRef = reactExports.useRef({
4296    active: false,
4297    startTime: 0,
4298    duration: 2200
4299  });
4300  const salivaDripRef = reactExports.useRef({
4301    active: false,
4302    startTime: 0,
4303    duration: 3200
4304  });
4305  const sweatFlyRef = reactExports.useRef({
4306    active: false,
4307    startTime: 0,
4308    duration: 2400
4309  });
4310  const prevHoveredRef = reactExports.useRef(false);
4311  const prevSelectedRef = reactExports.useRef(false);
4312  reactExports.useEffect(() => {
4313    const handleTrigger = (e) => {
4314      var _a2;
4315      if (!((_a2 = e.detail) == null ? void 0 : _a2.animalId) || e.detail.animalId === animal.id) {
4316        const n = typeof performance !== "undefined" ? performance.now() : Date.now();
4317        salivaDripRef.current = {
4318          active: true,
4319          startTime: n,
4320          duration: 3200
4321        };
4322        const tickLoop = () => {
4323          const cur = typeof performance !== "undefined" ? performance.now() : Date.now();
4324          if (cur - salivaDripRef.current.startTime < salivaDripRef.current.duration && salivaDripRef.current.active) {
4325            setWaveAnimTick((t) => (t + 1) % 1e6);
4326            requestAnimationFrame(tickLoop);
4327          } else {
4328            salivaDripRef.current.active = false;
4329            setWaveAnimTick(0);
4330          }
4331        };
4332        requestAnimationFrame(tickLoop);
4333      }
4334    };
4335    window.addEventListener("southpark_trigger_saliva", handleTrigger);
4336    return () => window.removeEventListener("southpark_trigger_saliva", handleTrigger);
4337  }, [animal.id]);
4338  reactExports.useEffect(() => {
4339    if (typeof window !== "undefined" && !window.__triggerSalivaDrip) {
4340      window.__triggerSalivaDrip = (animalId) => {
4341        window.dispatchEvent(new CustomEvent("southpark_trigger_saliva", { detail: { animalId } }));
4342      };
4343    }
4344  }, []);
4345  reactExports.useEffect(() => {
4346    const handleTrigger = (e) => {
4347      var _a2;
4348      if (!((_a2 = e.detail) == null ? void 0 : _a2.animalId) || e.detail.animalId === animal.id) {
4349        const n = typeof performance !== "undefined" ? performance.now() : Date.now();
4350        sweatFlyRef.current = {
4351          active: true,
4352          startTime: n,
4353          duration: 2400
4354        };
4355        const tickLoop = () => {
4356          const cur = typeof performance !== "undefined" ? performance.now() : Date.now();
4357          if (cur - sweatFlyRef.current.startTime < sweatFlyRef.current.duration && sweatFlyRef.current.active) {
4358            setWaveAnimTick((t) => (t + 1) % 1e6);
4359            requestAnimationFrame(tickLoop);
4360          } else {
4361            sweatFlyRef.current.active = false;
4362            setWaveAnimTick(0);
4363          }
4364        };
4365        requestAnimationFrame(tickLoop);
4366      }
4367    };
4368    window.addEventListener("southpark_trigger_sweat", handleTrigger);
4369    return () => window.removeEventListener("southpark_trigger_sweat", handleTrigger);
4370  }, [animal.id]);
4371  reactExports.useEffect(() => {
4372    if (typeof window !== "undefined" && !window.__triggerFlyingSweat) {
4373      window.__triggerFlyingSweat = (animalId) => {
4374        window.dispatchEvent(new CustomEvent("southpark_trigger_sweat", { detail: { animalId } }));
4375      };
4376    }
4377  }, []);
4378  reactExports.useEffect(() => {
4379    const justHovered = isHovered && !prevHoveredRef.current;
4380    const justSelected = isSelected && !prevSelectedRef.current;
4381    prevHoveredRef.current = !!isHovered;
4382    prevSelectedRef.current = !!isSelected;
4383    if (justHovered || justSelected) {
4384      const isEligible = !isPlaying && !isActive && restPosture !== "playing" && restPosture !== "anticipation";
4385      if (isEligible) {
4386        const start = typeof performance !== "undefined" ? performance.now() : Date.now();
4387        hoverWaveRef.current = {
4388          active: true,
4389          startTime: start,
4390          duration: 2200
4391        };
4392        let rafId;
4393        const tickLoop = () => {
4394          const nowTime = typeof performance !== "undefined" ? performance.now() : Date.now();
4395          if (nowTime - hoverWaveRef.current.startTime < hoverWaveRef.current.duration && hoverWaveRef.current.active) {
4396            setWaveAnimTick((t) => (t + 1) % 1e6);
4397            rafId = requestAnimationFrame(tickLoop);
4398          } else {
4399            hoverWaveRef.current.active = false;
4400            setWaveAnimTick(0);
4401          }
4402        };
4403        rafId = requestAnimationFrame(tickLoop);
4404        return () => cancelAnimationFrame(rafId);
4405      }
4406    }
4407  }, [isHovered, isSelected, isActive, restPosture]);
4408  reactExports.useEffect(() => {
4409    if (!isPlaying) {
4410      const timer = setInterval(() => {
4411        var _a2, _b2, _c2;
4412        const n = typeof performance !== "undefined" ? performance.now() : Date.now();
4413        const sched2 = idleScheduleRef.current;
4414        if (!sched2.currentAction && n >= sched2.nextTriggerTime) {
4415          const idleList = getIdleActionsList(actionsConfig);
4416          if (idleList && idleList.length > 0) {
4417            const picked = idleList[Math.floor(Math.random() * idleList.length)];
4418            sched2.currentAction = picked;
4419            sched2.startTime = n;
4420            sched2.duration = ((_a2 = picked.idleSchedule) == null ? void 0 : _a2.durationMs) ?? 2e3;
4421            sched2.blendIn = ((_b2 = picked.idleSchedule) == null ? void 0 : _b2.blendInMs) ?? 400;
4422            sched2.blendOut = ((_c2 = picked.idleSchedule) == null ? void 0 : _c2.blendOutMs) ?? 400;
4423            const tickIdle = () => {
4424              const cur = typeof performance !== "undefined" ? performance.now() : Date.now();
4425              if (sched2.currentAction && cur - sched2.startTime < sched2.duration) {
4426                setWaveAnimTick((t) => (t + 1) % 1e6);
4427                requestAnimationFrame(tickIdle);
4428              } else {
4429                setWaveAnimTick(0);
4430              }
4431            };
4432            requestAnimationFrame(tickIdle);
4433          }
4434        }
4435      }, 1e3);
4436      return () => clearInterval(timer);
4437    }
4438  }, [isPlaying, actionsConfig]);
4439  const springConfig = reactExports.useMemo(() => {
4440    if (physicsMode === "snappy") {
4441      return { type: "spring", stiffness: 400, damping: 18, mass: 0.5 };
4442    } else {
4443      return { type: "spring", stiffness: 180, damping: 16, mass: 0.7 };
4444    }
4445  }, [physicsMode]);
4446  const opacity = isHidden ? 0.2 : isMuted ? 0.35 : 1;
4447  const intensity = velocity > 0 ? Math.min(1, Math.max(0.2, velocity / 127)) : 0;
4448  const calibration = useCalibration(animal.id);
4449  const mergedOffsets = {
4450    headX: ((customConfig == null ? void 0 : customConfig.headX) ?? ((_a = animal == null ? void 0 : animal.offsets) == null ? void 0 : _a.headX) ?? 0) + ((calibration == null ? void 0 : calibration.headX) || 0),
4451    headY: ((customConfig == null ? void 0 : customConfig.headY) ?? ((_b = animal == null ? void 0 : animal.offsets) == null ? void 0 : _b.headY) ?? 0) + ((calibration == null ? void 0 : calibration.headY) || 0),
4452    armLeftX: ((customConfig == null ? void 0 : customConfig.armLeftX) ?? ((_c = animal == null ? void 0 : animal.offsets) == null ? void 0 : _c.armLeftX) ?? 0
4452) + ((calibration == null ? void 0 : calibration.armLeftX) || 0),
4453    armLeftY: ((customConfig == null ? void 0 : customConfig.armLeftY) ?? ((_d = animal == null ? void 0 : animal.offsets) == null ? void 0 : _d.armLeftY) ?? 0) + ((calibration == null ? void 0 : calibration.armLeftY) || 0),
4454    armRightX: ((customConfig == null ? void 0 : customConfig.armRightX) ?? ((_e = animal == null ? void 0 : animal.offsets) == null ? void 0 : _e.armRightX) ?? 0) + ((calibration == null ? void 0 : calibration.armRightX) || 0),
4455    armRightY: ((customConfig == null ? void 0 : customConfig.armRightY) ?? ((_f = animal == null ? void 0 : animal.offsets) == null ? void 0 : _f.armRightY) ?? 0) + ((calibration == null ? void 0 : calibration.armRightY) || 0),
4456    scale: ((customConfig == null ? void 0 : customConfig.scale) ?? ((_g = animal == null ? void 0 : animal.offsets) == null ? void 0 : _g.scale) ?? 1) * ((calibration == null ? void 0 : calibration.scale) || 1)
4457  };
4458  const geom = ANIMAL_GEOMETRY[animal.id] || ANIMAL_GEOMETRY.fox;
4459  const shoulderL = {
4460    x: geom.shoulderL.x + (isNaN(mergedOffsets.armLeftX) ? 0 : mergedOffsets.armLeftX),
4461    y: geom.shoulderL.y + (isNaN(mergedOffsets.armLeftY) ? 0 : mergedOffsets.armLeftY)
4462  };
4463  const shoulderR = {
4464    x: geom.shoulderR.x + (isNaN(mergedOffsets.armRightX) ? 0 : mergedOffsets.armRightX),
4465    y: geom.shoulderR.y + (isNaN(mergedOffsets.armRightY) ? 0 : mergedOffsets.armRightY)
4466  };
4467  const variantScaleMultiplier = (variant == null ? void 0 : variant.scaleMultiplier) ?? 1;
4468  const safeScale = Math.max(0.1, isNaN(mergedOffsets.scale) || mergedOffsets.scale <= 0 ? 1 : mergedOffsets.scale) * variantScaleMultiplier;
4469  const headW = geom.head.w * safeScale;
4470  const headH = geom.head.h * safeScale;
4471  const headX = geom.head.x + (isNaN(mergedOffsets.headX) ? 0 : mergedOffsets.headX);
4472  const headY = geom.head.y + (isNaN(mergedOffsets.headY) ? 0 : mergedOffsets.headY);
4473  const headCenterX = headX + headW / 2;
4474  const headCenterY = headY + headH * 0.45;
4475  const mouthOffset = ANIMAL_MOUTH_OFFSETS[animal.id] || { xRatio: 0.03, yRatio: 0.82 };
4476  const mouthOriginX = headCenterX + headW * mouthOffset.xRatio;
4477  const mouthOriginY = headY + headH * mouthOffset.yRatio;
4478  const browOriginY = headY + headH * 0.36;
4479  const resolvedCostumes = reactExports.useMemo(() => {
4480    const list = [
4481      ...(customConfig == null ? void 0 : customConfig.costumeIds) || [],
4482      ...costumes || (variant == null ? void 0 : variant.costumeIds) || []
4483    ];
4484    return resolveCostumesList(list, customCostumesMap);
4485  }, [costumes, customConfig == null ? void 0 : customConfig.costumeIds, customCostumesMap, variant == null ? void 0 : variant.costumeIds]);
4486  const backBodyCostumes = reactExports.useMemo(() => resolvedCostumes.filter((c) => c.type === "body" && c.behindBody), [resolvedCostumes]);
4487  const bodyCostumes = reactExports.useMemo(() => resolvedCostumes.filter((c) => c.type === "body" && !c.behindBody), [resolvedCostumes]);
4488  const neckCostumes = reactExports.useMemo(() => resolvedCostumes.filter((c) => c.type === "neck"), [resolvedCostumes]);
4489  const headCostumes = reactExports.useMemo(() => resolvedCostumes.filter((c) => c.type === "hat" || c.type === "glasses"), [resolvedCostumes]);
4490  const renderCostume = (c, idx) => {
4491    const scaleVal = c.scale || 1;
4492    const hatTiltOffset = c.type === "hat" ? (variant == null ? void 0 : variant.hatTilt) || 0 : 0;
4493    const rot = (c.rotation || 0) + hatTiltOffset;
4494    const ox = c.offsetX || 0;
4495    const oy = c.offsetY || 0;
4496    if (c.svgContent) {
4497      return /* @__PURE__ */ jsxRuntimeExports.jsx(
4498        "g",
4499        {
4500          id: `costume-${c.id}`,
4501          transform: `translate(${ox}, ${oy}) rotate(${rot}) scale(${scaleVal})`,
4502          dangerouslySetInnerHTML: { __html: c.svgContent }
4503        },
4504        `${c.id}-${idx}`
4505      );
4506    }
4507    if (c.url) {
4508      const baseW = 90 * scaleVal;
4509      const baseH = 90 * scaleVal;
4510      return /* @__PURE__ */ jsxRuntimeExports.jsx(
4511        "image",
4512        {
4513          id: `costume-${c.id}`,
4514          href: c.url,
4515          x: ox - baseW / 2,
4516          y: oy - baseH / 2,
4517          width: baseW,
4518          height: baseH,
4519          transform: rot !== 0 ? `rotate(${rot}, ${ox}, ${oy})` : void 0,
4520          preserveAspectRatio: "xMidYMid meet"
4521        },
4522        `${c.id}-${idx}`
4523      );
4524    }
4525    return null;
4526  };
4527  const hold = (instrument == null ? void 0 : instrument.holdStyle) || "standard";
4528  const currentBpm = bpm || 120;
4529  const currentBps = bps || currentBpm / 60;
4530  const beatPhase = (currentTime || 0) * currentBps * Math.PI * 2;
4531  const restWeight = (musicalFeatures == null ? void 0 : musicalFeatures.restTransitionWeight) !== void 0 ? musicalFeatures.restTransitionWeight : !isActive ? 1 : 0;
4532  const tempoBounce = isPlaying ? Math.abs(Math.sin(beatPhase)) * (isActive ? -6 : restPosture === "ready_hold" ? -3 : -1.5) : Math.sin((currentTime || 0) * 1.8) * 0.8;
4533  const tempoHeadTilt = isPlaying ? Math.sin(beatPhase * 0.5) * (isActive ? 4 : restPosture === "ready_hold" ? 2.5 : 1.2) : Math.sin((currentTime || 0) * 0.9) * 0.6;
4534  const tempoHeadBobY = isPlaying ? Math.sin(beatPhase) * (isActive ? 3 : restPosture === "ready_hold" ? 1.5 : 0.8) : Math.sin((currentTime || 0) * 1.8) * 0.5;
4535  const basePose = getInstrumentCalibratedBasePose(instrument, hold);
4536  let currentPose = {
4537    targetL: { ...basePose.targetL },
4538    targetR: { ...basePose.targetR },
4539    wristTiltL: basePose.wristTiltL,
4540    wristTiltR: basePose.wristTiltR,
4541    bodyBounce: tempoBounce,
4542    bodySquash: basePose.bodySquashFactor,
4543    bodySwayX: 0,
4544    instrumentRotation: instrument.rotation !== void 0 ? instrument.rotation : basePose.instrumentRotation || 0,
4545    headTilt: tempoHeadTilt,
4546    headBobY: tempoHeadBobY,
4547    mouthOpen: 0
4548  };
4549  const evalState = {
4550    isActive,
4551    velocity,
4552    activePitch: activePitch || 60,
4553    currentTime: currentTime || 0,
4554    beatPhase,
4555    intensity,
4556    isPlaying: !!isPlaying,
4557    noteDensity: musicalFeatures == null ? void 0 : musicalFeatures.noteDensity,
4558    polyphony: musicalFeatures == null ? void 0 : musicalFeatures.polyphony,
4559    velocityEnvelope: musicalFeatures == null ? void 0 : musicalFeatures.velocityEnvelope,
4560    pitchRange: musicalFeatures == null ? void 0 : musicalFeatures.pitchRange,
4561    pitchCenter: musicalFeatures == null ? void 0 : musicalFeatures.pitchCenter,
4562    articulationType: musicalFeatures == null ? void 0 : musicalFeatures.articulationType,
4563    restDuration: musicalFeatures == null ? void 0 : musicalFeatures.restDuration,
4564    isClimaxing: musicalFeatures == null ? void 0 : musicalFeatures.isClimaxing,
4565    isPhraseAttack: musicalFeatures == null ? void 0 : musicalFeatures.isPhraseAttack,
4566    phraseAttackDecay: musicalFeatures == null ? void 0 : musicalFeatures.phraseAttackDecay,
4567    tempoMultiplier: musicalFeatures == null ? void 0 : musicalFeatures.tempoMultiplier
4568  };
4569  const activeActions = getActionsForInstrument(instrument.program, hold, actionsConfig);
4570  for (const act of activeActions) {
4571    currentPose = evaluateActionChannels(act.channels, currentPose, evalState, 1);
4572  }
4573  const isPlayingInstrument = showInstrument && hold !== "standard" && hold !== "static" && hold !== "clapping" && (!!instrument.svgContent || !!instrument.imgUrl);
4574  const now = typeof performance !== "undefined" ? performance.now() : Date.now();
4575  const sched = idleScheduleRef.current;
4576  if (isActive || restPosture === "anticipation" || restPosture === "playing" || (musicalFeatures == null ? void 0 : musicalFeatures.isClimaxing)) {
4577    if (sched.currentAction && sched.currentAction.id !== "idle_saliva_drip" && sched.currentAction.id !== "idle_flying_sweat") {
4578      sched.currentAction = null;
4579      sched.nextTriggerTime = now + 8e3 + Math.random() * 15e3;
4580    }
4581  } else {
4582    const isEligibleForIdle = restPosture === "relaxed_rest" || !isPlaying;
4583    if (isEligibleForIdle && !sched.currentAction && now >= sched.nextTriggerTime) {
4584      const rawIdleList = getIdleActionsList(actionsConfig);
4585      const idleList = isPlaying ? rawIdleList.filter((act) => act.id !== "idle_friendly_wave") : rawIdleList;
4586      if (idleList && idleList.length > 0) {
4587        const picked = idleList[Math.floor(Math.random() * idleList.length)];
4588        sched.currentAction = picked;
4589        sched.startTime = now;
4590        sched.duration = ((_h = picked.idleSchedule) == null ? void 0 : _h.durationMs) ?? 2e3;
4591        sched.blendIn = ((_i = picked.idleSchedule) == null ? void 0 : _i.blendInMs) ?? 400;
4592        sched.blendOut = ((_j = picked.idleSchedule) == null ? void 0 : _j.blendOutMs) ?? 400;
4593      }
4594    }
4595    if (sched.currentAction) {
4596      const elapsed = now - sched.startTime;
4597      const idleProgress = Math.max(0, Math.min(1, elapsed / sched.duration));
4598      let idleWeight = 1;
4599      if (elapsed < sched.blendIn) {
4600        idleWeight = elapsed / Math.max(1, sched.blendIn);
4601      } else if (elapsed > sched.duration - sched.blendOut) {
4602        idleWeight = Math.max(0, (sched.duration - elapsed) / Math.max(1, sched.blendOut));
4603      }
4604      sched.currentWeight = idleWeight;
4605      if (elapsed >= sched.duration) {
4606        const minI = ((_k = sched.currentAction.idleSchedule) == null ? void 0 : _k.minIntervalMs) ?? 1e4;
4607        const maxI = ((_l = sched.currentAction.idleSchedule) == null ? void 0 : _l.maxIntervalMs) ?? 28e3;
4608        sched.currentAction = null;
4609        sched.currentWeight = 0;
4610        sched.nextTriggerTime = now + minI + Math.random() * (maxI - minI);
4611      } else if (idleWeight > 0) {
4612        const idleEvalState = {
4613          ...evalState,
4614          idleProgress
4615        };
4616        const idleChannels = isPlayingInstrument && sched.currentAction.id !== "idle_friendly_wave" ? sched.currentAction.channels.filter((c) => c.target !== "targetL" && c.target !== "targetR") : sched.currentAction.channels;
4617        currentPose = evaluateActionChannels(idleChannels, currentPose, idleEvalState, idleWeight);
4618      }
4619    }
4620  }
4621  let targetL = currentPose.targetL;
4622  let targetR = currentPose.targetR;
4623  let bendL = basePose.bendL;
4624  let bendR = basePose.bendR;
4625  let wristTiltL = currentPose.wristTiltL;
4626  let wristTiltR = currentPose.wristTiltR;
4627  let bodyBounce = currentPose.bodyBounce;
4628  let bodySquash = currentPose.bodySquash;
4629  let bodySwayX = currentPose.bodySwayX;
4630  let instrumentRotation = currentPose.instrumentRotation;
4631  let hasBow = !!basePose.hasBow;
4632  const isBlowing = hold === "blowing" || hold === "flute" || hold === "harmonica";
4633  const isViolinHold = hold === "violin" || hold === "pizzicato";
4634  const isWoodwind = instrument.program >= 64 && instrument.program <= 71 || instrument.program >= 72 && instrument.program <= 79;
4635  const isMallet = ((_m = instrument.jointDef) == null ? void 0 : _m.jointType) === "mallet_bars";
4636  const isTubularBells = instrument.program === 14;
4637  const isKeyboard = ((_n = instrument.jointDef) == null ? void 0 : _n.jointType) === "seated_keys" || hold === "sitting" && !isMallet && !isTubularBells || instrument.program === 8 || instrument.program >= 0 && instrument.program <= 7 || instrument.program >= 16 && instrument.program <= 20;
4638  if (restWeight > 0 && isBlowing) {
4639    targetL = { x: targetL.x * (1 - 0.15 * restWeight), y: targetL.y + 24 * restWeight };
4640    targetR = { x: targetR.x * (1 - 0.15 * restWeight), y: targetR.y + 24 * restWeight };
4641    wristTiltL = wristTiltL * (1 - 0.5 * restWeight);
4642    wristTiltR = wristTiltR * (1 - 0.5 * restWeight);
4643  } else if (restWeight > 0 && (isKeyboard || hold === "sitting" || hold === "keyboard")) {
4644    targetL = { x: targetL.x * (1 - 0.05 * restWeight), y: targetL.y - 4 * restWeight };
4645    targetR = { x: targetR.x * (1 - 0.05 * restWeight), y: targetR.y - 4 * restWeight };
4646  } else if (restWeight > 0 && isViolinHold) {
4647    targetR = { x: targetR.x + 8 * restWeight, y: targetR.y + 16 * restWeight };
4648  } else if (restWeight > 0 && hold === "drumming") {
4649    targetL = { x: targetL.x, y: targetL.y + 14 * restWeight };
4650    targetR = { x: targetR.x, y: targetR.y + 14 * restWeight };
4651  }
4652  let hasSticks = !!basePose.hasSticks || isMallet || ((_o = instrument.jointDef) == null ? void 0 : _o.jointType) === "drum_sticks";
4653  if (instrument.program === 57 && isActive) {
4654    targetR = { ...targetR, x: targetR.x + 14 * intensity };
4655  }
4656  const isWavingEligible = !isPlaying && !isActive && restPosture !== "playing" && restPosture !== "anticipation";
4657  let hoverWaveWeight = 0;
4658  if (hoverWaveRef.current.active && isWavingEligible) {
4659    const elapsed = now - hoverWaveRef.current.startTime;
4660    if (elapsed >= hoverWaveRef.current.duration) {
4661      hoverWaveRef.current.active = false;
4662    } else {
4663      const blendIn = 250;
4664      const blendOut = 350;
4665      if (elapsed < blendIn) {
4666        hoverWaveWeight = elapsed / blendIn;
4667      } else if (elapsed > hoverWaveRef.current.duration - blendOut) {
4668        hoverWaveWeight = Math.max(0, (hoverWaveRef.current.duration - elapsed) / blendOut);
4669      } else {
4670        hoverWaveWeight = 1;
4671      }
4672    }
4673  }
4674  const isIdleWave = ((_p = sched.currentAction) == null ? void 0 : _p.id) === "idle_friendly_wave" && isWavingEligible;
4675  const idleWaveWeight = isIdleWave ? sched.currentWeight || 0 : 0;
4676  const waveWeight = Math.max(hoverWaveWeight, idleWaveWeight);
4677  if (waveWeight > 0) {
4678    const totalArmLen = geom.upperR.len + geom.lowerR.len;
4679    const waveTargetR = {
4680      x: shoulderR.x + totalArmLen * 0.68,
4681      y: shoulderR.y - totalArmLen * 0.5
4682    };
4683    targetR = {
4684      x: targetR.x * (1 - waveWeight) + waveTargetR.x * waveWeight,
4685      y: targetR.y * (1 - waveWeight) + waveTargetR.y * waveWeight
4686    };
4687    if (waveWeight > 0.3) {
4688      bendR = 1;
4689    }
4690    const animTimeSec = currentTime && currentTime > 0 ? currentTime : now / 1e3;
4691    const waveWaggle = Math.sin(animTimeSec * 28) * 22;
4692    wristTiltR = wristTiltR * (1 - waveWeight) + waveWaggle * waveWeight;
4693    currentPose.headTilt = (currentPose.headTilt || 0) - 4 * waveWeight;
4694    currentPose.mouthOpen = Math.max(currentPose.mouthOpen || 0, 0.35 * waveWeight);
4695  }
4696  let effectiveSalivaProgress = 0;
4697  if (((_q = sched.currentAction) == null ? void 0 : _q.id) === "idle_saliva_drip") {
4698    const elapsed = now - sched.startTime;
4699    effectiveSalivaProgress = Math.max(0, Math.min(1, elapsed / (sched.duration || 3200)));
4700  } else if (salivaDripRef.current.active) {
4701    const elapsed = now - salivaDripRef.current.startTime;
4702    if (elapsed >= salivaDripRef.current.duration) {
4703      salivaDripRef.current.active = false;
4704    } else {
4705      effectiveSalivaProgress = Math.max(0, Math.min(1, elapsed / salivaDripRef.current.duration));
4706    }
4707  } else if (currentPose.salivaDrip && currentPose.salivaDrip > 0) {
4708    effectiveSalivaProgress = currentPose.salivaDrip;
4709  }
4710  if (effectiveSalivaProgress > 0) {
4711    const tiltCurve = Math.sin(effectiveSalivaProgress * Math.PI);
4712    currentPose.headTilt = (currentPose.headTilt || 0) + tiltCurve * 4.2;
4713    currentPose.mouthOpen = Math.max(currentPose.mouthOpen || 0, tiltCurve * 0.48);
4714  }
4715  let effectiveSweatProgress = 0;
4716  if (((_r = sched.currentAction) == null ? void 0 : _r.id) === "idle_flying_sweat") {
4717    const elapsed = now - sched.startTime;
4718    effectiveSweatProgress = Math.max(0, Math.min(1, elapsed / (sched.duration || 2400)));
4719  } else if (sweatFlyRef.current.active) {
4720    const elapsed = now - sweatFlyRef.current.startTime;
4721    if (elapsed >= sweatFlyRef.current.duration) {
4722      sweatFlyRef.current.active = false;
4723    } else {
4724      effectiveSweatProgress = Math.max(0, Math.min(1, elapsed / sweatFlyRef.current.duration));
4725    }
4726  } else if (currentPose.sweatFly && currentPose.sweatFly > 0) {
4727    effectiveSweatProgress = currentPose.sweatFly;
4728  } else if (isActive && ((musicalFeatures == null ? void 0 : musicalFeatures.isClimaxing) || ((musicalFeatures == null ? void 0 : musicalFeatures.noteDensity) || 0) > 6.5)) {
4729    const cycleProgress = now / 1e3 * (bps || 2) % 1;
4730    effectiveSweatProgress = cycleProgress;
4731  }
4732  `/assets/visualizers/southpark/animals/${animal.id}_upper_arm_l.webp`;
4733  `/assets/visualizers/southpark/animals/${animal.id}_lower_arm_l.webp`;
4734  const handFileL = `/assets/visualizers/southpark/animals/${animal.id}_hand_l.webp`;
4735  `/assets/visualizers/southpark/animals/${animal.id}_upper_arm_r.webp`;
4736  `/assets/visualizers/southpark/animals/${animal.id}_lower_arm_r.webp`;
4737  const handFileR = `/assets/visualizers/southpark/animals/${animal.id}_hand_r.webp`;
4738  const bodyUrl = `/assets/visualizers/southpark/animals/${animal.id}_body.webp`;
4739  const headUrl = `/assets/visualizers/southpark/animals/${animal.id}_head.webp`;
4740  const resL = solve2BoneIK(shoulderL, targetL, geom.upperL.len, geom.lowerL.len, bendL);
4741  const resR = solve2BoneIK(shoulderR, targetR, geom.upperR.len, geom.lowerR.len, bendR);
4742  const rawShoulderAngleL = resL.shoulderAngle - 90;
4743  const rawElbowAngleL = resL.elbowAngle - 90;
4744  let normShoulderAngleL = rawShoulderAngleL;
4745  while (normShoulderAngleL > 180) normShoulderAngleL -= 360;
4746  while (normShoulderAngleL < -180) normShoulderAngleL += 360;
4747  let rawRelElbowL = rawElbowAngleL - rawShoulderAngleL;
4748  while (rawRelElbowL > 180) rawRelElbowL -= 360;
4749  while (rawRelElbowL < -180) rawRelElbowL += 360;
4750  const rawShoulderAngleR = resR.shoulderAngle - 90;
4751  const rawElbowAngleR = resR.elbowAngle - 90;
4752  let normShoulderAngleR = rawShoulderAngleR;
4753  while (normShoulderAngleR > 180) normShoulderAngleR -= 360;
4754  while (normShoulderAngleR < -180) normShoulderAngleR += 360;
4755  let rawRelElbowR = rawElbowAngleR - rawShoulderAngleR;
4756  while (rawRelElbowR > 180) rawRelElbowR -= 360;
4757  while (rawRelElbowR < -180) rawRelElbowR += 360;
4758  const baseShoulderAngleL = clampShoulderAngle(normShoulderAngleL, "left");
4759  const baseClampedRelElbowL = clampElbowAngle(rawRelElbowL, "left");
4760  const baseClampedWristTiltL = clampWristAngle(wristTiltL, "left");
4761  const baseShoulderAngleR = clampShoulderAngle(normShoulderAngleR, "right");
4762  const baseClampedRelElbowR = clampElbowAngle(rawRelElbowR, "right");
4763  const baseClampedWristTiltR = clampWristAngle(wristTiltR, "right");
4764  const shoulderAngleL = (manualLimbAngles == null ? void 0 : manualLimbAngles.shoulderL) !== void 0 ? manualLimbAngles.shoulderL : baseShoulderAngleL;
4765  const clampedRelElbowL = (manualLimbAngles == null ? void 0 : manualLimbAngles.relElbowL) !== void 0 ? clampElbowAngle(manualLimbAngles.relElbowL, "left") : baseClampedRelElbowL;
4766  const clampedWristTiltL = (manualLimbAngles == null ? void 0 : manualLimbAngles.wristL) !== void 0 ? clampWristAngle(manualLimbAngles.wristL, "left") : baseClampedWristTiltL;
4767  const shoulderAngleR = (manualLimbAngles == null ? void 0 : manualLimbAngles.shoulderR) !== void 0 ? manualLimbAngles.shoulderR : baseShoulderAngleR;
4768  const clampedRelElbowR = (manualLimbAngles == null ? void 0 : manualLimbAngles.relElbowR) !== void 0 ? clampElbowAngle(manualLimbAngles.relElbowR, "right") : baseClampedRelElbowR;
4769  const clampedWristTiltR = (manualLimbAngles == null ? void 0 : manualLimbAngles.wristR) !== void 0 ? clampWristAngle(manualLimbAngles.wristR, "right") : baseClampedWristTiltR;
4770  const elbowAngleR = shoulderAngleR + clampedRelElbowR;
4771  const furColor = (variant == null ? void 0 : variant.furColor) || animal.furColor || "#eb6a24";
4772  const sleeveColor = (variant == null ? void 0 : variant.sleeveColor) || animal.sleeveColor || furColor;
4773  const strokeColor = (variant == null ? void 0 : variant.strokeColor) || animal.strokeColor || "#1e1b18";
4774  const strokeWidth = (variant == null ? void 0 : variant.strokeWidth) ?? animal.strokeWidth ?? 2.5;
4775  const limbOpacity = skeletonOnly ? ghostSilhouette ? 0.35 : 0.08 : 1;
4776  const isSmallArm = geom.upperL.len <= 16;
4777  const rShoulder = isSmallArm ? 6.5 : 7.5;
4778  const rElbow = isSmallArm ? 6 : 7;
4779  const rWrist = isSmallArm ? 5 : 6;
4780  const armPathL = generateArmSleevePath(geom.upperL.len, geom.lowerL.len, clampedRelElbowL, rShoulder, rElbow, rWrist);
4781  const armPathR = generateArmSleevePath(geom.upperR.len, geom.lowerR.len, clampedRelElbowR, rShoulder, rElbow, rWrist);
4782  const leftArmRig = /* @__PURE__ */ jsxRuntimeExports.jsx("g", { id: "rig-left-arm", transform: `translate(${shoulderL.x}, ${shoulderL.y})`, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { transform: `rotate(${shoulderAngleL})`, children: [
4783    !skeletonOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
4784      "path",
4785      {
4786        d: armPathL,
4787        fill: sleeveColor,
4788        stroke: strokeColor,
4789        strokeWidth,
4790        strokeLinejoin: "round",
4791        strokeLinecap: "round",
4792        opacity: limbOpacity
4793      }
4794    ),
4795    skeletonOnly && ghostSilhouette && /* @__PURE__ */ jsxRuntimeExports.jsx(
4796      "path",
4797      {
4798        d: armPathL,
4799        fill: sleeveColor,
4800        stroke: "none",
4801        opacity: 0.35
4802      }
4803    ),
4804    (showSkeleton || skeletonOnly) && /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { style: { pointerEvents: "none" }, children: [
4805      /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: 0, y1: 0, x2: 0, y2: geom.upperL.len, stroke: "#10b981", strokeWidth: 4, strokeLinecap: "round" }),
4806      /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: 0, cy: 0, r: 5, fill: "#fbbf24", stroke: "#ffffff", strokeWidth: 1.5 }),
4807      /* @__PURE__ */ jsxRuntimeExports.jsx("g", { style: { filter: "drop-shadow(0 1px 2px rgba(0,0,0,0.8))" }, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("text", { x: -10, y: -4, fill: "#fde047", fontSize: 7, fontWeight: "bold", fontFamily: "monospace", textAnchor: "end", children: [
4808        Math.round(shoulderAngleL),
4809        "°"
4810      ] }) })
4811    ] }),
4812    /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { transform: `translate(0, ${geom.upperL.len}) rotate(${clampedRelElbowL})`, children: [
4813      (showSkeleton || skeletonOnly) && /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { style: { pointerEvents: "none" }, children: [
4814        /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: 0, y1: 0, x2: 0, y2: geom.lowerL.len, stroke: "#10b981", strokeWidth: 3.5, strokeLinecap: "round" }),
4815        /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: 0, cy: 0, r: 4.5, fill: "#ef4444", stroke: "#ffffff", strokeWidth: 1.5 }),
4816        /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { style: { filter: "drop-shadow(0 1px 2px rgba(0,0,0,0.8))" }, children: [
4817          /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: -22, y: -10, width: 18, height: 9, rx: 2, fill: "#064e3b", fillOpacity: 0.9, stroke: "#10b981", strokeWidth: 0.8 }),
4818          /* @__PURE__ */ jsxRuntimeExports.jsxs("text", { x: -13, y: -3.5, fill: "#a7f3d0", fontSize: 7, fontWeight: "bold", fontFamily: "monospace", textAnchor: "middle", children: [
4819            Math.round(clampedRelElbowL),
4820            "°"
4821          ] })
4822        ] })
4823      ] }),
4824      /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { transform: `translate(0, ${geom.lowerL.len}) rotate(${clampedWristTiltL})`, children: [
4825        !skeletonOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
4826          "image",
4827          {
4828            href: handFileL,
4829            x: geom.handL.x,
4830            y: geom.handL.y,
4831            width: geom.handL.w,
4832            height: geom.handL.h,
4833            preserveAspectRatio: "xMidYMid meet",
4834            opacity: limbOpacity,
4835            filter: variantFilterId ? `url(#${variantFilterId})` : void 0
4836          }
4837        ),
4838        (showSkeleton || skeletonOnly) && /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { style: { pointerEvents: "none" }, children: [
4839          /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: 0, y1: 0, x2: 0, y2: 12, stroke: "#34d399", strokeWidth: 2.5, strokeLinecap: "round" }),
4840          /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: 0, cy: 0, r: 4, fill: "#10b981", stroke: "#ffffff", strokeWidth: 1.5 }),
4841          /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: 0, cy: 12, r: 2.5, fill: "#6ee7b7" })
4842        ] }),
4843        hasBow && !skeletonOnly && (() => {
4844          const bowAngleOffset = hold === "violin" ? -30 : 0;
4845          const bowRot = -(shoulderAngleL + clampedRelElbowL + clampedWristTiltL) + bowAngleOffset;
4846          return /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { id: "articulated-bow", transform: `translate(0, 8) rotate(${bowRot})`, children: [
4847            /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "-10", y: "-4", width: "14", height: "8", rx: "1.5", fill: "#18181b", stroke: "#000000", strokeWidth: 1 }),
4848            /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "-12", cy: "0", r: "2.5", fill: "#cbd5e1", stroke: "#475569", strokeWidth: 0.8 }),
4849            /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "-7", y: "-1.5", width: "7", height: "3", rx: "0.5", fill: "#f8fafc" }),
4850            /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "-12", y1: "0", x2: hold === "violin" ? 56 : 72, y2: "0", stroke: "#78350f", strokeWidth: 3.2, strokeLinecap: "round" }),
4851            /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "-8", y1: "3", x2: hold === "violin" ? 52 : 68, y2: "2", stroke: "#ffffff", strokeWidth: 1.8, strokeLinecap: "round" }),
4852            /* @__PURE__ */ jsxRuntimeExports.jsx("polygon", { points: `${hold === "violin" ? 52 : 68},0 ${hold === "violin" ? 56 : 72},-1.5 ${hold === "violin" ? 56 : 72},2 ${hold === "violin" ? 53 : 69},3`, fill: "#fef3c7", stroke: "#78350f", strokeWidth: 0.8 })
4853          ] });
4854        })(),
4855        hasSticks && !isTubularBells && !skeletonOnly && (() => {
4856          var _a2;
4857          const isMallet2 = ((_a2 = instrument.jointDef) == null ? void 0 : _a2.jointType) === "mallet_bars";
4858          const strikeAngle = isActive ? 18 * intensity : 0;
4859          if (isMallet2) {
4860            const isTimpani = instrument.program === 47;
4861            return /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { transform: `translate(0, 4) rotate(${28 + strikeAngle})`, children: [
4862              /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "0", y1: "0", x2: "0", y2: isTimpani ? 22 : 18, stroke: isTimpani ? "#78350f" : "#ca8a04", strokeWidth: 2.8, strokeLinecap: "round" }),
4863              /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "0", cy: isTimpani ? 22 : 18, r: isTimpani ? 4.5 : 4, fill: isTimpani ? "#f8fafc" : "#38bdf8", stroke: "#1e293b", strokeWidth: 1 })
4864            ] });
4865          }
4866          const isTaiko = instrument.program === 116;
4867          const stickStroke = isTaiko ? "#f59e0b" : isMallet2 ? "#ca8a04" : "#e2e8f0";
4868          const stickTip = isTaiko ? "#fef08a" : "#cbd5e1";
4869          const stickRot = (manualLimbAngles == null ? void 0 : manualLimbAngles.stickL) !== void 0 ? manualLimbAngles.stickL : -35 - strikeAngle;
4870          const stickLen = isTaiko ? 24 : isMallet2 ? 26 : 34;
4871          return /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { transform: `translate(0, ${isTaiko ? 6 : 8}) rotate(${stickRot})`, children: [
4872            /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "0", y1: "0", x2: "0", y2: stickLen, stroke: stickStroke, strokeWidth: isTaiko ? 3.8 : 3, strokeLinecap: "round" }),
4873            /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "0", cy: stickLen, r: isTaiko ? 3.2 : 3, fill: stickTip, stroke: isTaiko ? "#d97706" : void 0, strokeWidth: isTaiko ? 0.8 : 0 })
4874          ] });
4875        })()
4876      ] })
4877    ] })
4878  ] }) });
4879  const rightArmRig = /* @__PURE__ */ jsxRuntimeExports.jsx("g", { id: "rig-right-arm", transform: `translate(${shoulderR.x}, ${shoulderR.y})`, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { transform: `rotate(${shoulderAngleR})`, children: [
4880    !skeletonOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
4881      "path",
4882      {
4883        d: armPathR,
4884        fill: sleeveColor,
4885        stroke: strokeColor,
4886        strokeWidth,
4887        strokeLinejoin: "round",
4888        strokeLinecap: "round",
4889        opacity: limbOpacity
4890      }
4891    ),
4892    skeletonOnly && ghostSilhouette && /* @__PURE__ */ jsxRuntimeExports.jsx(
4893      "path",
4894      {
4895        d: armPathR,
4896        fill: sleeveColor,
4897        stroke: "none",
4898        opacity: 0.35
4899      }
4900    ),
4901    (showSkeleton || skeletonOnly) && /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { style: { pointerEvents: "none" }, children: [
4902      /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: 0, y1: 0, x2: 0, y2: geom.upperR.len, stroke: "#8b5cf6", strokeWidth: 4, strokeLinecap: "round" }),
4903      /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: 0, cy: 0, r: 5, fill: "#fbbf24", stroke: "#ffffff", strokeWidth: 1.5 }),
4904      /* @__PURE__ */ jsxRuntimeExports.jsx("g", { style: { filter: "drop-shadow(0 1px 2px rgba(0,0,0,0.8))" }, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("text", { x: 10, y: -4, fill: "#fde047", fontSize: 7, fontWeight: "bold", fontFamily: "monospace", textAnchor: "start", children: [
4905        Math.round(shoulderAngleR),
4906        "°"
4907      ] }) })
4908    ] }),
4909    /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { transform: `translate(0, ${geom.upperR.len}) rotate(${clampedRelElbowR})`, children: [
4910      (showSkeleton || skeletonOnly) && /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { style: { pointerEvents: "none" }, children: [
4911        /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: 0, y1: 0, x2: 0, y2: geom.lowerR.len, stroke: "#8b5cf6", strokeWidth: 3.5, strokeLinecap: "round" }),
4912        /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: 0, cy: 0, r: 4.5, fill: "#3b82f6", stroke: "#ffffff", strokeWidth: 1.5 }),
4913        /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { style: { filter: "drop-shadow(0 1px 2px rgba(0,0,0,0.8))" }, children: [
4914          /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: 4, y: -10, width: 20, height: 9, rx: 2, fill: "#4c1d95", fillOpacity: 0.9, stroke: "#8b5cf6", strokeWidth: 0.8 }),
4915          /* @__PURE__ */ jsxRuntimeExports.jsxs("text", { x: 14, y: -3.5, fill: "#ddd6fe", fontSize: 7, fontWeight: "bold", fontFamily: "monospace", textAnchor: "middle", children: [
4916            "+",
4917            Math.round(clampedRelElbowR),
4918            "°"
4919          ] })
4920        ] })
4921      ] }),
4922      /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { transform: `translate(0, ${geom.lowerR.len}) rotate(${clampedWristTiltR})`, children: [
4923        !skeletonOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
4924          "image",
4925          {
4926            href: handFileR,
4927            x: geom.handR.x,
4928            y: geom.handR.y,
4929            width: geom.handR.w,
4930            height: geom.handR.h,
4931            preserveAspectRatio: "xMidYMid meet",
4932            opacity: limbOpacity,
4933            filter: variantFilterId ? `url(#${variantFilterId})` : void 0
4934          }
4935        ),
4936        (showSkeleton || skeletonOnly) && /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { style: { pointerEvents: "none" }, children: [
4937          /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: 0, y1: 0, x2: 0, y2: 12, stroke: "#a78bfa", strokeWidth: 2.5, strokeLinecap: "round" }),
4938          /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: 0, cy: 0, r: 4, fill: "#8b5cf6", stroke: "#ffffff", strokeWidth: 1.5 }),
4939          /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: 0, cy: 12, r: 2.5, fill: "#c4b5fd" })
4940        ] }),
4941        hasSticks && !skeletonOnly && (() => {
4942          var _a2;
4943          const isMallet2 = ((_a2 = instrument.jointDef) == null ? void 0 : _a2.jointType) === "mallet_bars";
4944          if (isTubularBells) {
4945            const chimeStrike = isActive ? -14 * intensity : 0;
4946            return /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { transform: `translate(0, 4) rotate(${-15 + chimeStrike})`, children: [
4947              /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "0", y1: "0", x2: "0", y2: "-22", stroke: "#78350f", strokeWidth: 3, strokeLinecap: "round" }),
4948              /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "-6", y: "-28", width: "12", height: "9", rx: "2", fill: "#fef3c7", stroke: "#1c1917", strokeWidth: 1 })
4949            ] });
4950          }
4951          const strikeAngle = isActive ? 18 * intensity : 0;
4952          if (isMallet2) {
4953            const isTimpani = instrument.program === 47;
4954            return /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { transform: `translate(0, 4) rotate(${-28 - strikeAngle})`, children: [
4955              /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "0", y1: "0", x2: "0", y2: isTimpani ? 22 : 18, stroke: isTimpani ? "#78350f" : "#ca8a04", strokeWidth: 2.8, strokeLinecap: "round" }),
4956              /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "0", cy: isTimpani ? 22 : 18, r: isTimpani ? 4.5 : 4, fill: isTimpani ? "#f8fafc" : "#38bdf8", stroke: "#1e293b", strokeWidth: 1 })
4957            ] });
4958          }
4959          const isTaiko = instrument.program === 116;
4960          const stickStroke = isTaiko ? "#f59e0b" : isMallet2 ? "#ca8a04" : "#e2e8f0";
4961          const stickTip = isTaiko ? "#fef08a" : "#cbd5e1";
4962          const stickRot = (manualLimbAngles == null ? void 0 : manualLimbAngles.stickR) !== void 0 ? manualLimbAngles.stickR : 35 + strikeAngle;
4963          const stickLen = isTaiko ? 24 : isMallet2 ? 26 : 34;
4964          return /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { transform: `translate(0, ${isTaiko ? 6 : 8}) rotate(${stickRot})`, children: [
4965            /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "0", y1: "0", x2: "0", y2: stickLen, stroke: stickStroke, strokeWidth: isTaiko ? 3.8 : 3, strokeLinecap: "round" }),
4966            /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "0", cy: stickLen, r: isTaiko ? 3.2 : 3, fill: stickTip, stroke: isTaiko ? "#d97706" : void 0, strokeWidth: isTaiko ? 0.8 : 0 })
4967          ] });
4968        })(),
4969        instrument.program === 55 && !skeletonOnly && /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { id: "conductor-baton", transform: "translate(0, 4) rotate(-35)", children: [
4970          /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: "0", cy: "0", rx: "3", ry: "5", fill: "#ca8a04", stroke: "#1c1917", strokeWidth: "0.8" }),
4971          /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "0", y1: "0", x2: "0", y2: "-32", stroke: "#ffffff", strokeWidth: "2.4", strokeLinecap: "round" }),
4972          /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "0", cy: "-32", r: "1.8", fill: "#ffffff" })
4973        ] })
4974      ] })
4975    ] })
4976  ] }) });
4977  const upperArmPathR = generateUpperArmSleevePath(geom.upperR.len, rShoulder, rElbow);
4978  const lowerArmPathR = generateLowerArmSleevePath(geom.lowerR.len, rElbow, rWrist);
4979  const rightUpperArmRig = /* @__PURE__ */ jsxRuntimeExports.jsx("g", { id: "rig-right-upper-arm", transform: `translate(${shoulderR.x}, ${shoulderR.y})`, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { transform: `rotate(${shoulderAngleR})`, children: [
4980    !skeletonOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
4981      "path",
4982      {
4983        d: upperArmPathR,
4984        fill: sleeveColor,
4985        stroke: strokeColor,
4986        strokeWidth,
4987        strokeLinejoin: "round",
4988        strokeLinecap: "round",
4989        opacity: limbOpacity
4990      }
4991    ),
4992    skeletonOnly && ghostSilhouette && /* @__PURE__ */ jsxRuntimeExports.jsx(
4993      "path",
4994      {
4995        d: upperArmPathR,
4996        fill: sleeveColor,
4997        stroke: "none",
4998        opacity: 0.35
4999      }
5000    ),
5001    (showSkeleton || skeletonOnly) && /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { style: { pointerEvents: "none" }, children: [
5002      /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: 0, y1: 0, x2: 0, y2: geom.upperR.len, stroke: "#8b5cf6", strokeWidth: 4, strokeLinecap: "round" }),
5003      /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: 0, cy: 0, r: 5, fill: "#fbbf24", stroke: "#ffffff", strokeWidth: 1.5 }),
5004      /* @__PURE__ */ jsxRuntimeExports.jsx("g", { style: { filter: "drop-shadow(0 1px 2px rgba(0,0,0,0.8))" }, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("text", { x: 10, y: -4, fill: "#fde047", fontSize: 7, fontWeight: "bold", fontFamily: "monospace", textAnchor: "start", children: [
5005        Math.round(shoulderAngleR),
5006        "°"
5007      ] }) })
5008    ] })
5009  ] }) });
5010  const rightLowerArmRig = /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { id: "rig-right-lower-arm", transform: `translate(${resR.elbow.x}, ${resR.elbow.y}) rotate(${elbowAngleR})`, children: [
5011    !skeletonOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
5012      "path",
5013      {
5014        d: lowerArmPathR,
5015        fill: sleeveColor,
5016        stroke: strokeColor,
5017        strokeWidth,
5018        strokeLinejoin: "round",
5019        strokeLinecap: "round",
5020        opacity: limbOpacity
5021      }
5022    ),
5023    skeletonOnly && ghostSilhouette && /* @__PURE__ */ jsxRuntimeExports.jsx(
5024      "path",
5025      {
5026        d: lowerArmPathR,
5027        fill: sleeveColor,
5028        stroke: "none",
5029        opacity: 0.35
5030      }
5031    ),
5032    (showSkeleton || skeletonOnly) && /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { style: { pointerEvents: "none" }, children: [
5033      /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: 0, y1: 0, x2: 0, y2: geom.lowerR.len, stroke: "#8b5cf6", strokeWidth: 3.5, strokeLinecap: "round" }),
5034      /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: 0, cy: 0, r: 4.5, fill: "#3b82f6", stroke: "#ffffff", strokeWidth: 1.5 }),
5035      /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { style: { filter: "drop-shadow(0 1px 2px rgba(0,0,0,0.8))" }, children: [
5036        /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: 4, y: -10, width: 20, height: 9, rx: 2, fill: "#4c1d95", fillOpacity: 0.9, stroke: "#8b5cf6", strokeWidth: 0.8 }),
5037        /* @__PURE__ */ jsxRuntimeExports.jsxs("text", { x: 14, y: -3.5, fill: "#ddd6fe", fontSize: 7, fontWeight: "bold", fontFamily: "monospace", textAnchor: "middle", children: [
5038          "+",
5039          Math.round(clampedRelElbowR),
5040          "°"
5041        ] })
5042      ] })
5043    ] }),
5044    /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { transform: `translate(0, ${geom.lowerR.len}) rotate(${clampedWristTiltR})`, children: [
5045      !skeletonOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
5046        "image",
5047        {
5048          href: handFileR,
5049          x: geom.handR.x,
5050          y: geom.handR.y,
5051          width: geom.handR.w,
5052          height: geom.handR.h,
5053          preserveAspectRatio: "xMidYMid meet",
5054          opacity: limbOpacity,
5055          filter: variantFilterId ? `url(#${variantFilterId})` : void 0
5056        }
5057      ),
5058      (showSkeleton || skeletonOnly) && /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { style: { pointerEvents: "none" }, children: [
5059        /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: 0, y1: 0, x2: 0, y2: 12, stroke: "#a78bfa", strokeWidth: 2.5, strokeLinecap: "round" }),
5060        /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: 0, cy: 0, r: 4, fill: "#8b5cf6", stroke: "#ffffff", strokeWidth: 1.5 }),
5061        /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: 0, cy: 12, r: 2.5, fill: "#c4b5fd" })
5062      ] })
5063    ] })
5064  ] });
5065  const instScale = instrument.scale || 1;
5066  let instW = 160 * instScale;
5067  let instH = 160 * instScale;
5068  let instX = -(instW / 2) + (instrument.offsetX || 0);
5069  let instY = 44 - 0.5 * instH + (instrument.offsetY || 0);
5070  let instCounterBounce = 0;
5071  if (hold === "sitting") {
5072    const isSynth = instrument.program >= 80 && instrument.program <= 103;
5073    if (isSynth) {
5074      instW = 160 * instScale;
5075      instH = 160 * instScale;
5076      instX = -(instW / 2) + (instrument.offsetX || 0);
5077      instY = 44 - 0.5 * instH + (instrument.offsetY || 0);
5078    } else if (isMallet) {
5079      if (isTubularBells) {
5080        instW = 185 * instScale;
5081        instH = 185 * instScale;
5082        instX = -(instW / 2) + (instrument.offsetX || 0);
5083        instY = 120 - 0.941 * instH + (instrument.offsetY || 0);
5084      } else {
5085        instW = (instrument.program === 11 || instrument.program === 12 ? 245 : 220) * instScale;
5086        instH = 216 * instScale;
5087        instX = -(instW / 2) + (instrument.offsetX || 0);
5088        instY = 120 - 0.941 * instH + (instrument.offsetY || 0);
5089      }
5090    } else if (instrument.program === 52 || instrument.program === 53 || instrument.program === 55) {
5091      if (instrument.program === 55) {
5092        instW = 160 * instScale;
5093        instH = 160 * instScale;
5094        instX = -(instW / 2) + (instrument.offsetX || 0);
5095        instY = 118 - 0.951 * instH + (instrument.offsetY || 0);
5096      } else {
5097        instW = 210 * instScale;
5098        instH = 210 * instScale;
5099        instX = -(instW / 2) + (instrument.offsetX || 0);
5100        instY = 120 - 0.945 * instH + (instrument.offsetY || 0);
5101      }
5102    } else {
5103      const isCustomSynth = instrument.program === 50 || instrument.program === 51 || instrument.program === 54 || instrument.program === 62 || instrument.program === 63 || instrument.program >= 80 && instrument.program <= 103;
5104      if (isCustomSynth) {
5105        instW = 200 * instScale;
5106        instH = 200 * instScale;
5107        instX = -(instW / 2) + (instrument.offsetX || 0);
5108        instY = 44 - 0.5625 * instH + (instrument.offsetY || 0);
5109      } else {
5110        instW = (instrument.program === 19 ? 245 : instrument.program === 8 || instrument.program >= 16 && instrument.program <= 20 || instrument.program === 38 || instrument.program === 39 ? 220 : 210) * instScale;
5111        instH = (instrument.program === 19 ? 225 : 216) * instScale;
5112        instX = -(instW / 2) + (instrument.offsetX || 0);
5113        instY = 120 - 0.941 * instH + (instrument.offsetY || 0);
5114      }
5115    }
5116    instCounterBounce = isKeyboard ? 0 : -bodyBounce;
5117  } else if (hold === "harmonica") {
5118    instW = 82 * instScale;
5119    instH = 82 * instScale;
5120    instX = -(instW / 2) + (instrument.offsetX || 0);
5121    const restDropY = 26 * restWeight;
5122    instY = -6 - 0.529 * instH + (instrument.offsetY || 0) + restDropY;
5123    if (instrument.rotation === void 0) instrumentRotation = 0;
5124  } else if (hold === "accordion") {
5125    instW = 155 * instScale;
5126    instH = 155 * instScale;
5127    instX = -(instW / 2) + (instrument.offsetX || 0);
5128    instY = 34 - 0.55 * instH + (instrument.offsetY || 0);
5129  } else if (hold === "drumming") {
5130    if (instrument.program === 47 || instrument.program >= 114 && instrument.program <= 119) {
5131      instW = 220 * instScale;
5132      instH = 220 * instScale;
5133      instX = -(instW / 2) + (instrument.offsetX || 0);
5134      instY = 120 - 0.947 * instH + (instrument.offsetY || 0);
5135      instCounterBounce = -bodyBounce;
5136    } else {
5137      instW = 310 * instScale;
5138      instH = 310 * instScale;
5139      instX = -(instW / 2) + (instrument.offsetX || 0);
5140      instY = 92 - 0.746 * instH + (instrument.offsetY || 0);
5141      instCounterBounce = -bodyBounce;
5142    }
5143  } else if (hold === "bowing") {
5144    const isDoubleBass = instrument.program === 32 || instrument.program === 43;
5145    const baseW = isDoubleBass ? 230 : 210;
5146    const baseH = isDoubleBass ? 250 : 210;
5147    instW = baseW * instScale;
5148    instH = baseH * instScale;
5149    instX = -(instW / 2) + (isDoubleBass ? -2 : 0) + (instrument.offsetX || 0);
5150    instY = 120 - (isDoubleBass ? 0.957 : 0.947) * instH + (instrument.offsetY || 0
5150);
5151    instCounterBounce = -bodyBounce;
5152  } else if (hold === "guitar") {
5153    instW = 200 * instScale;
5154    instH = 200 * instScale;
5155    instX = -100 + (instrument.offsetX || 0);
5156    instY = -65 + (instrument.offsetY || 0);
5157  } else if (hold === "blowing" || hold === "flute") {
5158    const isBigBrass = instrument.program === 58;
5159    const isBigWoodwind = instrument.program === 70;
5160    const isWoodwind2 = instrument.program >= 64 && instrument.program <= 71 || instrument.program >= 72 && instrument.program <= 79;
5161    let baseSize = 160;
5162    if (isBigBrass) {
5163      baseSize = 200;
5164    } else if (isBigWoodwind) {
5165      baseSize = 220;
5166    } else if (instrument.program === 67) {
5167      baseSize = 180;
5168    } else if (instrument.program === 73) {
5169      baseSize = 210;
5170    } else if (instrument.program === 72) {
5171      baseSize = 180;
5172    } else if (instrument.program === 74 || instrument.program === 77 || instrument.program === 78) {
5173      baseSize = 200;
5174    } else if (instrument.program === 75 || instrument.program === 76 || instrument.program === 79) {
5175      baseSize = 185;
5176    } else if (instrument.program === 121 || instrument.program === 123) {
5177      baseSize = 180;
5178    } else if (isWoodwind2) {
5179      baseSize = 200;
5180    }
5181    instW = baseSize * instScale;
5182    instH = baseSize * instScale;
5183    instX = -(0.5 * instW) + (instrument.offsetX || 0);
5184    const restDropY = 26 * restWeight;
5185    instY = -6 - 0.3515625 * instH + (instrument.offsetY || 0) + restDropY;
5186    const restTilt = (isWoodwind2 ? 18 : 22) * restWeight;
5187    if (instrument.rotation === void 0) {
5188      instrumentRotation = restTilt;
5189    } else {
5190      instrumentRotation = instrument.rotation + restTilt;
5191    }
5192  } else if (hold === "violin" || hold === "pizzicato") {
5193    instW = 145 * instScale;
5194    instH = 145 * instScale;
5195    instX = -(instW / 2) + 16 + (instrument.offsetX || 0);
5196    instY = -(instH / 2) + 12 + (instrument.offsetY || 0);
5197    if (instrument.rotation === void 0) instrumentRotation = 0;
5198  } else if (hold === "harp") {
5199    instW = 230 * instScale;
5200    instH = 230 * instScale;
5201    instX = -(instW / 2) + 6 + (instrument.offsetX || 0);
5202    instY = 118 - 0.966 * instH + (instrument.offsetY || 0);
5203    instCounterBounce = -bodyBounce;
5204  } else if (hold === "congas") {
5205    instW = 180 * instScale;
5206    instH = 180 * instScale;
5207    instX = -(instW / 2) + (instrument.offsetX || 0);
5208    instY = 38 - 0.45 * instH + (instrument.offsetY || 0);
5209    instCounterBounce = -bodyBounce;
5210  } else if (hold === "percussion_handheld") {
5211    if (instrument.program === 10) {
5212      instW = 85 * instScale;
5213      instH = 85 * instScale;
5214      instX = -(instW / 2) + (instrument.offsetX || 0);
5215      instY = 2 + (instrument.offsetY || 0);
5216    } else if (instrument.program === 108) {
5217      instW = 125 * instScale;
5218      instH = 125 * instScale;
5219      instX = -(instW / 2) + (instrument.offsetX || 0);
5220      instY = 44 - 0.5 * instH + (instrument.offsetY || 0);
5221    } else if (instrument.program === 112) {
5222      instW = 120 * instScale;
5223      instH = 120 * instScale;
5224      instX = -(instW / 2) + 16 + (instrument.offsetX || 0);
5225      instY = 22 - 0.24 * instH + (instrument.offsetY || 0);
5226    } else if (instrument.program === 113) {
5227      instW = 140 * instScale;
5228      instH = 140 * instScale;
5229      instX = -(instW / 2) + (instrument.offsetX || 0);
5230      instY = 28 - 0.35 * instH + (instrument.offsetY || 0);
5231    } else if (instrument.program === 115) {
5232      instW = 125 * instScale;
5233      instH = 125 * instScale;
5234      instX = -(instW / 2) + (instrument.offsetX || 0);
5235      instY = 34 - 0.5 * instH + (instrument.offsetY || 0);
5236    } else if (instrument.program === 122) {
5237      instW = 140 * instScale;
5238      instH = 140 * instScale;
5239      instX = -(instW / 2) + (instrument.offsetX || 0);
5240      instY = 38 - 0.5 * instH + (instrument.offsetY || 0);
5241    } else if (instrument.program === 124) {
5242      instW = 135 * instScale;
5243      instH = 135 * instScale;
5244      instX = -(instW / 2) + (instrument.offsetX || 0);
5245      instY = 38 - 0.5 * instH + (instrument.offsetY || 0);
5246    } else if (instrument.program === 125) {
5247      instW = 145 * instScale;
5248      instH = 145 * instScale;
5249      instX = -(instW / 2) + (instrument.offsetX || 0);
5250      instY = 32 - 0.5 * instH + (instrument.offsetY || 0);
5251    } else if (instrument.program === 126) {
5252      instW = 140 * instScale;
5253      instH = 140 * instScale;
5254      instX = -(instW / 2) + (instrument.offsetX || 0);
5255      instY = 32 - 0.5 * instH + (instrument.offsetY || 0);
5256    } else if (instrument.program === 127) {
5257      instW = 135 * instScale;
5258      instH = 135 * instScale;
5259      instX = -(instW / 2) + 12 + (instrument.offsetX || 0);
5260      instY = 22 - 0.5 * instH + (instrument.offsetY || 0);
5261    } else {
5262      instW = 130 * instScale;
5263      instH = 130 * instScale;
5264      instX = -20 + (instrument.offsetX || 0);
5265      instY = 14 + (instrument.offsetY || 0);
5266    }
5267  } else if (hold === "clapping") {
5268    instW = 70 * instScale;
5269    instH = 70 * instScale;
5270    instX = -(instW / 2) + (instrument.offsetX || 0);
5271    instY = 10 + (instrument.offsetY || 0);
5272  }
5273  const keyboardInstrumentElement = !skeletonOnly && isPlayingInstrument && isKeyboard && (instrument.svgContent ? /* @__PURE__ */ jsxRuntimeExports.jsx(
5274    "g",
5275    {
5276      id: `ground-instrument-${animal.id}`,
5277      transform: void 0,
5278      dangerouslySetInnerHTML: { __html: instrument.svgContent }
5279    }
5280  ) : /* @__PURE__ */ jsxRuntimeExports.jsx(
5281    "image",
5282    {
5283      id: `ground-instrument-${animal.id}`,
5284      href: instrument.imgUrl,
5285      x: instX,
5286      y: instY,
5287      width: instW,
5288      height: instH,
5289      preserveAspectRatio: "xMidYMid meet"
5290    }
5291  ));
5292  const nonKeyboardInstrumentElement = !skeletonOnly && isPlayingInstrument && !isKeyboard && (instrument.svgContent ? /* @__PURE__ */ jsxRuntimeExports.jsx(
5293    motion.g,
5294    {
5295      transform: void 0,
5296      dangerouslySetInnerHTML: { __html: instrument.svgContent },
5297      animate: { rotate: instrumentRotation },
5298      transition: springConfig
5299    }
5300  ) : /* @__PURE__ */ jsxRuntimeExports.jsx(
5301    motion.image,
5302    {
5303      href: instrument.imgUrl,
5304      x: instX,
5305      y: instY,
5306      width: instW,
5307      height: instH,
5308      preserveAspectRatio: "xMidYMid meet",
5309      animate: {
5310        y: instCounterBounce,
5311        rotate: instrumentRotation,
5312        scaleX: hold === "accordion" && isActive ? 1 + 0.08 * intensity : 1
5313      },
5314      transition: springConfig,
5315      style: { transformOrigin: `${instX + instW / 2}px ${instY + instH / 2}px` }
5316    }
5317  ));
5318  const torsoSkeletonElement = (showSkeleton || skeletonOnly) && /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { id: "kinematic-torso-skeleton", style: { pointerEvents: "none" }, children: [
5319    /* @__PURE__ */ jsxRuntimeExports.jsx(
5320      "line",
5321      {
5322        x1: shoulderL.x,
5323        y1: shoulderL.y,
5324        x2: shoulderR.x,
5325        y2: shoulderR.y,
5326        stroke: "#06b6d4",
5327        strokeWidth: 3,
5328        strokeLinecap: "round"
5329      }
5330    ),
5331    /* @__PURE__ */ jsxRuntimeExports.jsx(
5332      "line",
5333      {
5334        x1: 0,
5335        y1: 10,
5336        x2: 0,
5337        y2: 90,
5338        stroke: "#06b6d4",
5339        strokeWidth: 3.5,
5340        strokeLinecap: "round"
5341      }
5342    ),
5343    /* @__PURE__ */ jsxRuntimeExports.jsx(
5344      "line",
5345      {
5346        x1: -22,
5347        y1: 90,
5348        x2: 22,
5349        y2: 90,
5350        stroke: "#06b6d4",
5351        strokeWidth: 3,
5352        strokeLinecap: "round"
5353      }
5354    ),
5355    /* @__PURE__ */ jsxRuntimeExports.jsx(
5356      "line",
5357      {
5358        x1: 0,
5359        y1: 10,
5360        x2: headX,
5361        y2: headY + headH * 0.45,
5362        stroke: "#06b6d4",
5363        strokeWidth: 2,
5364        strokeDasharray: "3 3"
5365      }
5366    ),
5367    /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: 0, cy: 10, r: 4, fill: "#06b6d4", stroke: "#ffffff", strokeWidth: 1 }),
5368    /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: 0, cy: 90, r: 4, fill: "#06b6d4", stroke: "#ffffff", strokeWidth: 1 }),
5369    /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: headX, cy: headY + headH * 0.45, r: 3, fill: "#06b6d4" })
5370  ] });
5371  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
5372    motion.g,
5373    {
5374      id: `character-${animal.id}`,
5375      opacity,
5376      onClick: (e) => {
5377        if (e.altKey) {
5378          e.stopPropagation();
5379          window.dispatchEvent(new CustomEvent("southpark_trigger_sweat", { detail: { animalId: animal.id } }));
5380          return;
5381        }
5382        if (e.shiftKey) {
5383          e.stopPropagation();
5384          window.dispatchEvent(new CustomEvent("southpark_trigger_saliva", { detail: { animalId: animal.id } }));
5385          return;
5386        }
5387        onClick == null ? void 0 : onClick(e);
5388      },
5389      onDoubleClick: (e) => {
5390        e.stopPropagation();
5391        window.dispatchEvent(new CustomEvent("southpark_trigger_saliva", { detail: { animalId: animal.id } }));
5392      },
5393      onContextMenu,
5394      onMouseEnter: () => onHover == null ? void 0 : onHover(true),
5395      onMouseLeave: () => onHover == null ? void 0 : onHover(false),
5396      animate: isKeyboard ? void 0 : {
5397        scale: safeScale * (isActive ? 1 + intensity * 0.03 : 1)
5398      },
5399      transform: isKeyboard && safeScale !== 1 ? `scale(${safeScale})` : void 0,
5400      transition: isKeyboard ? { duration: 0 } : springConfig,
5401      children: [
5402        /* @__PURE__ */ jsxRuntimeExports.jsx(
5403          motion.g,
5404          {
5405            animate: {
5406              x: bodySwayX,
5407              y: isKeyboard || manualLimbAngles ? 0 : bodyBounce,
5408              scaleY: isKeyboard || manualLimbAngles ? 1 : bodySquash * (isActive ? 1 + intensity * 0.03 : 1),
5409              scaleX: isKeyboard || manualLimbAngles ? 1 : 1 / Math.sqrt(bodySquash) * (isActive ? 1 + intensity * 0.03 : 1)
5410            },
5411            transition: springConfig,
5412            style: { transformOrigin: "50% 100%" },
5413            children: renderArmsOnly ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
5414              rightArmRig,
5415              leftArmRig
5416            ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
5417              skeletonOnly && /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { opacity: 0.35, stroke: "#0ea5e9", strokeDasharray: "3 3", fill: "#0ea5e9", fillOpacity: 0.05, children: [
5418                /* @__PURE__ */ jsxRuntimeExports.jsx("ellipse", { cx: 0, cy: 50, rx: 46, ry: 52, strokeWidth: 1 }),
5419                /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: headX, cy: headY + headH * 0.45, r: 32, strokeWidth: 1 })
5420              ] }),
5421              !skeletonOnly && backBodyCostumes.map((c, i) => /* @__PURE__ */ jsxRuntimeExports.jsx("g", { transform: "translate(0, 16)", children: renderCostume(c, i) }, c.id || i)),
5422              /* @__PURE__ */ jsxRuntimeExports.jsx(
5423                "image",
5424                {
5425                  href: bodyUrl,
5426                  x: geom.body.x,
5427                  y: geom.body.y,
5428                  width: geom.body.w,
5429                  height: geom.body.h,
5430                  preserveAspectRatio: "xMidYMid meet",
5431                  opacity: limbOpacity,
5432                  filter: variantFilterId ? `url(#${variantFilterId})` : void 0
5433                }
5434              ),
5435              !skeletonOnly && bodyCostumes.map((c, i) => /* @__PURE__ */ jsxRuntimeExports.jsx("g", { transform: "translate(0, 10)", children: renderCostume(c, i) }, c.id || i)),
5436              !skeletonOnly && neckCostumes.map((c, i) => /* @__PURE__ */ jsxRuntimeExports.jsx("g", { transform: "translate(0, 8)", children: renderCostume(c, i) }, c.id || i)),
5437              /* @__PURE__ */ jsxRuntimeExports.jsxs(
5438                motion.g,
5439                {
5440                  animate: {
5441                    rotate: (isActive ? isBlowing ? -4 : -2 : 0) + currentPose.headTilt,
5442                    y: (isActive ? 1 : 0) + currentPose.headBobY
5443                  },
5444                  transition: springConfig,
5445                  style: { transformOrigin: "50% 100%" },
5446                  children: [
5447                    /* @__PURE__ */ jsxRuntimeExports.jsx(
5448                      "image",
5449                      {
5450                        href: headUrl,
5451                        x: headX,
5452                        y: headY,
5453                        width: headW,
5454                        height: headH,
5455                        preserveAspectRatio: "xMidYMid meet",
5456                        opacity: limbOpacity,
5457                        filter: variantFilterId ? `url(#${variantFilterId})` : void 0
5458                      }
5459                    ),
5460                    currentPose.mouthOpen > 0.05 && /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { id: `character-mouth-aperture-${animal.id}`, opacity: Math.min(1, currentPose.mouthOpen * 2.2), children: [
5461                      /* @__PURE__ */ jsxRuntimeExports.jsx(
5462                        "ellipse",
5463                        {
5464                          cx: headCenterX,
5465                          cy: mouthOriginY,
5466                          rx: Math.max(1.5, 4 * currentPose.mouthOpen),
5467                          ry: Math.max(1, 2.8 * currentPose.mouthOpen),
5468                          fill: "#181512",
5469                          stroke: "#181512",
5470                          strokeWidth: 0.5
5471                        }
5472                      ),
5473                      /* @__PURE__ */ jsxRuntimeExports.jsx(
5474                        "ellipse",
5475                        {
5476                          cx: headCenterX,
5477                          cy: mouthOriginY + 1 * currentPose.mouthOpen,
5478                          rx: Math.max(1, 2.8 * currentPose.mouthOpen),
5479                          ry: Math.max(0.6, 1.6 * currentPose.mouthOpen),
5480                          fill: "#fb7185"
5481                        }
5482                      )
5483                    ] }),
5484                    renderSalivaDrip(effectiveSalivaProgress, mouthOriginX, mouthOriginY),
5485                    renderFlyingSweat(effectiveSweatProgress, headCenterX, browOriginY, headW),
5486                    !skeletonOnly && headCostumes.map((c, i) => /* @__PURE__ */ jsxRuntimeExports.jsx("g", { transform: `translate(${headCenterX}, ${headCenterY})`, children: renderCostume(c, i) }, c.id || i)),
5487                    isBlowing && !isWoodwind && nonKeyboardInstrumentElement
5488                  ]
5489                }
5490              ),
5491              !hideArms && (isViolinHold ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
5492                rightUpperArmRig,
5493                nonKeyboardInstrumentElement,
5494                rightLowerArmRig,
5495                leftArmRig
5496              ] }) : isWoodwind ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
5497                nonKeyboardInstrumentElement,
5498                rightArmRig,
5499                leftArmRig
5500              ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
5501                !isBlowing && nonKeyboardInstrumentElement,
5502                rightArmRig,
5503                leftArmRig
5504              ] })),
5505              torsoSkeletonElement
5506            ] })
5507          }
5508        ),
5509        isKeyboard && keyboardInstrumentElement,
5510        isSoloed && /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "0", cy: "0", r: "85", fill: "url(#soloGradient)", style: { pointerEvents: "none" } })
5511      ]
5512    }
5513  );
5514};
5515const SouthparkCharacter = React.memo(SouthparkCharacterComponent);
5516export {
5517  BASE_ANIMALS as B,
5518  DEFAULT_HOLD_STYLE_ACTIONS as D,
5519  PERCUSSION_INSTRUMENTS as P,
5520  SouthparkCharacter as S,
5521  PROGRAM_SPECIFIC_ACTIONS as a,
5522  DEFAULT_IDLE_ACTIONS as b,
5523  SOUTHPARK_TRUMPET_SVG as c,
5524  SOUTHPARK_PIANO_SVG as d,
5525  SOUTHPARK_DRUMS_SVG as e,
5526  SOUTHPARK_CELLO_SVG as f,
5527  getInstrumentDef as g,
5528  SOUTHPARK_GUITAR_SVG as h,
5529  DEFAULT_SOUTHPARK_COSTUMES as i,
5530  getAnimalForProgram as j,
5531  calibrationStore as k,
5532  SOUTHPARK_INSTRUMENTS as l
5533};

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.