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