PageSourceSearch

https://www.hamienet.com/assets/SouthparkInstruments-C-w5kAUh.js

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