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