1import { r as reactExports, j as jsxRuntimeExports } from "./vendor-react-Bay9Y7Lb.js"; 2import { a as defaultBackgroundPresets, d as defaultBackgroundConfig, B as BackgroundParticlesCanvas } from "./StagePuppetEngine-BtPqoUG7.js"; 3import { B as BASE_ANIMALS, g as getInstrumentDef, S as SouthparkCharacter } from "./SouthparkCharacter-BNwA9Wd0.js"; 4import { d as defaultSouthparkStageConfig, g as getSouthparkStageAlignment, c as calculateEnsemblePlacement, r as resolveLightingProfile, S as SouthparkStageBanner, a as SouthparkStageLighting } from "./SouthparkStageBanner-BCxT9awg.js"; 5import { bi as Sparkles, c3 as RotateCcw, c1 as Copy, bo as Monitor, bZ as Pause, bU as Play, bA as Eye, bz as EyeOff, bO as Compass, cj as Layers, f_ as Lightbulb, c6 as Shuffle, dK as Move, cB as ChevronDown, bE as ChevronRight, dN as Users, d7 as Image, bf as Search, b2 as Upload, b7 as Check, b5 as Trash2, df as SlidersVertical, bF as CircleQuestionMark } from "./vendor-dVNaLJx_.js"; 6import "./synth-BE1TSTD4.js"; 7import "./vendor-react-three-MDLA5jog.js"; 8import "./wasm-synth-adapter-BrqgBX1y.js"; 9import "./vendor-spessasynth-DH8n6o_V.js"; 10import "./vendor-audio-DH3w0BzW.js"; 11import "./IkMath-D6fi1ppd.js"; 12import "./vendor-gsap-50Lx19ak.js"; 13import "./vendor-framer-C8eEgkdL.js"; 14import "./vendor-i18n-R-bMoYp_.js"; 15const PARTICLE_OPTIONS = [ 16 { id: "stars", label: "Twinkling Stars", icon: "ð" }, 17 { id: "notes", label: "Musical Notes", icon: "ðµ" }, 18 { id: "snow", label: "Falling Snow", icon: "âï¸" }, 19 { id: "bubbles", label: "Floating Bubbles", icon: "ð«§" }, 20 { id: "rain", label: "Raindrops", icon: "ð§ï¸" }, 21 { id: "confetti", label: "Celebration Confetti", icon: "ð" }, 22 { id: "fireflies", label: "Glowing Fireflies", icon: "â¨" }, 23 { id: "hearts", label: "Floating Hearts", icon: "ð" }, 24 { id: "autumn_leaves", label: "Autumn Leaves", icon: "ð" }, 25 { id: "matrix", label: "Matrix Code Rain", icon: "ð»" }, 26 { id: "dust", label: "Atmospheric Dust", icon: "ð«ï¸" }, 27 { id: "sparkles", label: "Glittering Sparkles", icon: "â¨" }, 28 { id: "floating_orbs", label: "Floating Orbs", icon: "ð®" }, 29 { id: "laser_lines", label: "Laser Lines", icon: "â¡" } 30]; 31const POPULAR_SIMULATION_GENRES = [ 32 { label: "Anime & Animation", tag: "anime" }, 33 { label: "Classical & Orchestral", tag: "classical" }, 34 { label: "Rock & Metal", tag: "rock" }, 35 { label: "Jazz & Blues", tag: "jazz" }, 36 { label: "Game Music & Chiptune", tag: "game music" }, 37 { label: "Hip Hop & Rap", tag: "hip hop" }, 38 { label: "EDM & Techno", tag: "techno" }, 39 { label: "Folk & Acoustic", tag: "folk" }, 40 { label: "Latin & Salsa", tag: "latin" }, 41 { label: "Pop & Dance", tag: "pop" }, 42 { label: "Country & Western", tag: "country" }, 43 { label: "Synth & Sci-Fi", tag: "electronic" } 44]; 45const DEFAULT_STAGE_IMAGE = { 46 url: "", 47 size: "cover", 48 position: "center center", 49 repeat: "no-repeat", 50 blur: 0 51}; 52const DEFAULT_STAGE_OVERLAY = { 53 enabled: true, 54 color: "#000000", 55 opacity: 0.15 56}; 57const DEFAULT_STAGE_PARTICLES = { 58 enabled: false, 59 type: "stars", 60 count: 50, 61 speed: 1, 62 color: "#ffffff", 63 size: 2 64}; 65const SAMPLE_ENSEMBLE_POOL = [ 66 // 1 to 5: Quintet (Rhythm + Melodic Core) 67 { id: "fox_gtr", animalId: "fox", program: 25, isPercussion: false, familyIndex: 3, tier: "Front · Acoustic Guitar" }, 68 { id: "bear_drums", animalId: "bear", program: 0, isPercussion: true, familyIndex: -1, percRole: "drums", tier: "Back · Drum Kit" }, 69 { id: "wolf_cello", animalId: "wolf", program: 42, isPercussion: false, familyIndex: 5, tier: "Middle · Cello" }, 70 { id: "lion_trpt", animalId: "lion", program: 56, isPercussion: false, familyIndex: 7, tier: "Middle · Trumpet" }, 71 { id: "rabbit_vln", animalId: "rabbit", program: 40, isPercussion: false, familyIndex: 5, tier: "Front · Violin" }, 72 // 6 to 8: Chamber Octet 73 { id: "hamster_pno", animalId: "hamster", program: 0, isPercussion: false, familyIndex: 0, tier: "Back · Grand Piano" }, 74 { id: "wolf_bass", animalId: "wolf", program: 33, isPercussion: false, familyIndex: 4, tier: "Front · Electric Bass" }, 75 { id: "owl_flute", animalId: "owl", program: 73, isPercussion: false, familyIndex: 9, tier: "Middle · Flute" }, 76 // 9 to 12: Big Band 77 { id: "badger_sax", animalId: "badger", program: 65, isPercussion: false, familyIndex: 8, tier: "Middle · Alto Sax" }, 78 { id: "penguin_syn", animalId: "penguin", program: 81, isPercussion: false, familyIndex: 10, tier: "Back · Synth Lead" }, 79 { id: "deer_egtr", animalId: "deer", program: 29, isPercussion: false, familyIndex: 3, tier: "Front · Overdrive Guitar" }, 80 { id: "lion_tbne", animalId: "lion", program: 57, isPercussion: false, familyIndex: 7, tier: "Middle · Trombone" }, 81 // 13 to 18: Concert Orchestra 82 { id: "otter_harp", animalId: "otter", program: 46, isPercussion: false, familyIndex: 5, tier: "Middle · Concert Harp" }, 83 { id: "gorilla_conga", animalId: "gorilla", program: 0, isPercussion: true, familyIndex: -1, percRole: "congas", tier: "Back · Congas" }, 84 { id: "raccoon_tamb", animalId: "raccoon", program: 0, isPercussion: true, familyIndex: -1, percRole: "tambourine", tier: "Back · Tambourine" }, 85 { id: "llama_pan", animalId: "llama", program: 75, isPercussion: false, familyIndex: 9, tier: "Middle · Pan Flute" }, 86 { id: "koala_pad", animalId: "koala", program: 88, isPercussion: false, familyIndex: 11, tier: "Back · Warm Pad" }, 87 { id: "fox_solo", animalId: "fox", program: 27, isPercussion: false, familyIndex: 3, tier: "Front · Electric Solo" }, 88 // 19 to 24: Grand Symphony 89 { id: "bat_fx", animalId: "bat", program: 98, isPercussion: false, familyIndex: 12, tier: "Back · Crystal FX" }, 90 { id: "parrot_horn", animalId: "parrot", program: 60, isPercussion: false, familyIndex: 7, tier: "Middle · French Horn" }, 91 { id: "rabbit_viola", animalId: "rabbit", program: 41, isPercussion: false, familyIndex: 5, tier: "Front · Viola" }, 92 { id: "deer_tuba", animalId: "deer", program: 58, isPercussion: false, familyIndex: 7, tier: "Middle · Tuba" }, 93 { id: "bear_timp", animalId: "bear", program: 47, isPercussion: false, familyIndex: 1, tier: "Back · Timpani" }, 94 { id: "badger_clari", animalId: "badger", program: 71, isPercussion: false, familyIndex: 8, tier: "Middle · Clarinet" } 95]; 96const SouthparkStageDesigner = ({ 97 stageConfig, 98 setStageConfig,
99 spBackgroundConfig, 100 setSpBackgroundConfig, 101 spBackgroundPresets, 102 setSpBackgroundPresets, 103 sharedBackgroundConfig, 104 sharedBackgroundPresets, 105 customBackgrounds, 106 setCustomBackgrounds, 107 charactersConfig, 108 costumesConfig 109}) => { 110 var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V; 111 const [selectedPresetId, setSelectedPresetId] = reactExports.useState(null); 112 const [shufflePreviewIndex, setShufflePreviewIndex] = reactExports.useState(0); 113 const [galleryFilter, setGalleryFilter] = reactExports.useState("all"); 114 const [searchQuery, setSearchQuery] = reactExports.useState(""); 115 const [isParticlesPlaying, setIsParticlesPlaying] = reactExports.useState(true); 116 const [showSampleMusicians, setShowSampleMusicians] = reactExports.useState(true); 117 const [sampleMusicianCount, setSampleMusicianCount] = reactExports.useState(5); 118 const [enableDensityScaling, setEnableDensityScaling] = reactExports.useState(true); 119 const [showArcGuides, setShowArcGuides] = reactExports.useState(false); 120 const [showDepthGuides, setShowDepthGuides] = reactExports.useState(false); 121 const [previewPhysics, setPreviewPhysics] = reactExports.useState("snappy"); 122 const [previewLighting, setPreviewLighting] = reactExports.useState("dynamic"); 123 const [isDraggingStage, setIsDraggingStage] = reactExports.useState(false); 124 const dragStartRef = reactExports.useRef(null); 125 const [activeDepthDrag, setActiveDepthDrag] = reactExports.useState(null); 126 const svgViewportRef = reactExports.useRef(null); 127 const [openSections, setOpenSections] = reactExports.useState({ 128 depth: true, 129 // 3D Depth & Perspective - expanded by default 130 camera: true, 131 // Stage Camera & Pan/Zoom - expanded by default 132 geometry: false, 133 // Musicians & Seating Geometry 134 atmosphere: false 135 // Stage Lighting & Atmosphere 136 }); 137 const toggleSection = (key) => { 138 setOpenSections((prev) => ({ ...prev, [key]: !prev[key] })); 139 }; 140 const [simulationTagInput, setSimulationTagInput] = reactExports.useState("classical"); 141 const [uploadError, setUploadError] = reactExports.useState(null); 142 const [isUploading, setIsUploading] = reactExports.useState(false); 143 const fileInputRef = reactExports.useRef(null); 144 const mergedPresets = reactExports.useMemo(() => { 145 const basePool = defaultBackgroundPresets.filter((p) => { 146 var _a2, _b2, _c2; 147 return !((_c2 = (_b2 = (_a2 = p.config) == null ? void 0 : _a2.image) == null ? void 0 : _b2.url) == null ? void 0 : _c2.includes("background-forest")); 148 }); 149 return basePool.map((base) => { 150 const spOverride = spBackgroundPresets.find((p) => p.id === base.id); 151 return { 152 id: base.id, 153 name: spOverride ? spOverride.name : base.name, 154 tags: spOverride ? spOverride.tags : base.tags, 155 config: spOverride ? spOverride.config : base.config, 156 isSpOverride: !!spOverride 157 }; 158 }); 159 }, [spBackgroundPresets]); 160 const activeConfig = reactExports.useMemo(() => { 161 if (selectedPresetId === null) { 162 if (mergedPresets.length > 0) { 163 const previewPreset = mergedPresets[shufflePreviewIndex % mergedPresets.length]; 164 return (previewPreset == null ? void 0 : previewPreset.config) || defaultSouthparkStageConfig; 165 } 166 return defaultSouthparkStageConfig; 167 } 168 const preset = mergedPresets.find((p) => p.id === selectedPresetId); 169 return (preset == null ? void 0 : preset.config) || defaultSouthparkStageConfig; 170 }, [selectedPresetId, mergedPresets, shufflePreviewIndex]); 171 const isCurrentSelectedSpOverride = reactExports.useMemo(() => { 172 var _a2; 173 if (selectedPresetId === null) { 174 const targetId = (_a2 = mergedPresets[shufflePreviewIndex % mergedPresets.length]) == null ? void 0 : _a2.id; 175 return targetId ? spBackgroundPresets.some((p) => p.id === targetId) : false; 176 } 177 return spBackgroundPresets.some((p) => p.id === selectedPresetId); 178 }, [selectedPresetId, spBackgroundPresets, mergedPresets, shufflePreviewIndex]); 179 const activePresetMeta = reactExports.useMemo(() => { 180 if (selectedPresetId === null) { 181 const previewPreset = mergedPresets[shufflePreviewIndex % mergedPresets.length]; 182 return { 183 id: "__random__", 184 name: `Dynamic Stage Shuffle (${(previewPreset == null ? void 0 : previewPreset.name) || "Random Stage"})`, 185 tags: ["(Untagged songs randomly select from all active stage presets)"], 186 isSpOverride: false 187 }; 188 } 189 return mergedPresets.find((p) => p.id === selectedPresetId) || { 190 id: selectedPresetId, 191 name: "Custom Preset", 192 tags: [], 193 isSpOverride: isCurrentSelectedSpOverride 194 }; 195 }, [selectedPresetId, mergedPresets, isCurrentSelectedSpOverride, shufflePreviewIndex]); 196 const updateActiveConfig = (updater) => { 197 var _a2; 198 const targetId = selectedPresetId === null ? (_a2 = mergedPresets[shufflePreviewIndex % mergedPresets.length]) == null ? void 0 : _a2.id : selectedPresetId; 199 if (!targetId) return;
200 setSpBackgroundPresets((prevPresets) => { 201 var _a3; 202 const existingIdx = prevPresets.findIndex((p) => p.id === targetId); 203 const basePreset = defaultBackgroundPresets.find((p) => p.id === targetId); 204 const currentConf = existingIdx >= 0 && ((_a3 = prevPresets[existingIdx]) == null ? void 0 : _a3.config) ? prevPresets[existingIdx].config : (basePreset == null ? void 0 : basePreset.config) || defaultSouthparkStageConfig; 205 const newConf = updater(currentConf); 206 if (existingIdx >= 0) { 207 const next = [...prevPresets]; 208 next[existingIdx] = { ...next[existingIdx], config: newConf }; 209 return next; 210 } else { 211 return [ 212 ...prevPresets, 213 { 214 id: targetId, 215 name: (basePreset == null ? void 0 : basePreset.name) || "Custom Preset", 216 tags: (basePreset == null ? void 0 : basePreset.tags) || [], 217 config: newConf 218 } 219 ]; 220 } 221 }); 222 }; 223 const updatePresetMeta = (name, tags) => { 224 var _a2; 225 const targetId = selectedPresetId === null ? (_a2 = mergedPresets[shufflePreviewIndex % mergedPresets.length]) == null ? void 0 : _a2.id : selectedPresetId; 226 if (!targetId) return; 227 setSpBackgroundPresets((prevPresets) => { 228 const existingIdx = prevPresets.findIndex((p) => p.id === targetId); 229 const currentPreset = mergedPresets.find((p) => p.id === targetId); 230 const baseName = name !== void 0 ? name : (currentPreset == null ? void 0 : currentPreset.name) || "Preset"; 231 const baseTags = tags !== void 0 ? tags : (currentPreset == null ? void 0 : currentPreset.tags) || []; 232 const baseConf = (currentPreset == null ? void 0 : currentPreset.config) || defaultBackgroundConfig; 233 if (existingIdx >= 0) { 234 const next = [...prevPresets]; 235 next[existingIdx] = { 236 ...next[existingIdx], 237 name: baseName, 238 tags: baseTags 239 }; 240 return next; 241 } else { 242 return [ 243 ...prevPresets, 244 { 245 id: targetId, 246 name: baseName, 247 tags: baseTags, 248 config: baseConf 249 } 250 ]; 251 } 252 }); 253 }; 254 const handleRevertToShared = () => { 255 var _a2; 256 const targetId = selectedPresetId === null ? (_a2 = mergedPresets[shufflePreviewIndex % mergedPresets.length]) == null ? void 0 : _a2.id : selectedPresetId; 257 if (targetId) { 258 setSpBackgroundPresets((prev) => prev.filter((p) => p.id !== targetId)); 259 } 260 }; 261 const handleUploadBackground = async (e) => { 262 var _a2; 263 const file = (_a2 = e.target.files) == null ? void 0 : _a2[0]; 264 if (!file) return; 265 setIsUploading(true); 266 setUploadError(null); 267 const formData = new FormData(); 268 formData.append("background", file); 269 try { 270 const res = await fetch("/api/admin/upload-global-background", { 271 method: "POST", 272 headers: { "Authorization": "Bearer " + localStorage.getItem("admin_token") }, 273 body: formData 274 }); 275 if (!res.ok) { 276 const errData = await res.json(); 277 throw new Error(errData.error || "Upload failed"); 278 } 279 const data = await res.json(); 280 setCustomBackgrounds((prev) => [...prev, data.url]); 281 updateActiveConfig((prev) => ({ 282 ...prev, 283 type: "image", 284 image: { 285 ...prev.image || DEFAULT_STAGE_IMAGE, 286 url: data.url 287 } 288 })); 289 } catch (err) { 290 setUploadError(err.message || "Failed to upload background"); 291 } finally { 292 setIsUploading(false); 293 if (fileInputRef.current) fileInputRef.current.value = ""; 294 } 295 }; 296 const handleDeleteCustomBackground = async (bgUrl) => { 297 if (!confirm("Are you sure you want to delete this custom background from the gallery?")) return; 298 const filename = bgUrl.split("/").pop(); 299 if (!filename) return; 300 try { 301 const res = await fetch(`/api/admin/backgrounds/${filename}`, { 302 method: "DELETE", 303 headers: { "Authorization": "Bearer " + localStorage.getItem("admin_token") } 304 }); 305 if (!res.ok) { 306 const err = await res.json(); 307 throw new Error(err.error || "Delete failed"); 308 }
309 setCustomBackgrounds((prev) => prev.filter((url) => url !== bgUrl)); 310 } catch (err) { 311 alert(err.message || "Failed to delete"); 312 } 313 }; 314 const simulationResult = reactExports.useMemo(() => { 315 const query = simulationTagInput.toLowerCase().trim(); 316 if (!query) return null; 317 const spMatch = spBackgroundPresets.find( 318 (p) => p.tags && p.tags.some((t) => t.toLowerCase().includes(query) || query.includes(t.toLowerCase())) 319 ); 320 if (spMatch) { 321 return { 322 presetId: spMatch.id, 323 presetName: spMatch.name, 324 reason: `Direct South Park Tag Match on "${query}"`, 325 isSp: true, 326 config: spMatch.config 327 }; 328 } 329 const sharedPool = sharedBackgroundPresets && sharedBackgroundPresets.length > 0 ? sharedBackgroundPresets : defaultBackgroundPresets; 330 const sharedMatch = sharedPool.find( 331 (p) => p.tags && p.tags.some((t) => t.toLowerCase().includes(query) || query.includes(t.toLowerCase())) 332 ); 333 if (sharedMatch) { 334 return { 335 presetId: sharedMatch.id, 336 presetName: sharedMatch.name, 337 reason: `Shared Orchestra Tag Match on "${query}"`, 338 isSp: false, 339 config: sharedMatch.config 340 }; 341 } 342 let hash = 0; 343 for (let i = 0; i < query.length; i++) { 344 hash = query.charCodeAt(i) + ((hash << 5) - hash); 345 } 346 const fallbackIdx = Math.abs(hash) % sharedPool.length; 347 const fallbackPreset = sharedPool[fallbackIdx]; 348 return { 349 presetId: (fallbackPreset == null ? void 0 : fallbackPreset.id) || "classical", 350 presetName: (fallbackPreset == null ? void 0 : fallbackPreset.name) || "Default", 351 reason: `Dynamic Stage Pool Fallback (unmatched songs randomly select across stage pool)`, 352 isSp: false, 353 config: (fallbackPreset == null ? void 0 : fallbackPreset.config) || defaultBackgroundConfig 354 }; 355 }, [simulationTagInput, spBackgroundPresets, sharedBackgroundPresets]); 356 const stageBackgroundStyle = reactExports.useMemo(() => { 357 var _a2, _b2, _c2, _d2, _e2, _f2, _g2, _h2, _i2, _j2; 358 const bg = activeConfig; 359 if (!bg) return { backgroundColor: "#0c0c14" }; 360 if (bg.type === "color") { 361 return { backgroundColor: bg.color || "#0c0c14" }; 362 } else if (bg.type === "gradient" && ((_a2 = bg.gradient) == null ? void 0 : _a2.stops)) { 363 const { type, angle, stops } = bg.gradient; 364 const stopStr = stops.map((s) => `${s.color} ${s.offset}%`).join(", "); 365 return { 366 background: type === "radial" ? `radial-gradient(circle, ${stopStr})` : `linear-gradient(${angle ?? 180}deg, ${stopStr})` 367 }; 368 } else { 369 const stageCalib = getSouthparkStageAlignment((_b2 = bg.image) == null ? void 0 : _b2.url); 370 const scale = ((_c2 = bg.image) == null ? void 0 : _c2.scale) !== void 0 ? bg.image.scale : stageCalib.scale; 371 const offsetX = ((_d2 = bg.image) == null ? void 0 : _d2.offsetX) !== void 0 ? bg.image.offsetX : stageCalib.offsetX || 0; 372 const offsetY = ((_e2 = bg.image) == null ? void 0 : _e2.offsetY) !== void 0 ? bg.image.offsetY : stageCalib.offsetY; 373 return { 374 backgroundColor: bg.color || "#050505", 375 backgroundImage: ((_f2 = bg.image) == null ? void 0 : _f2.url) ? `url("${bg.image.url}")` : void 0, 376 backgroundSize: ((_g2 = bg.image) == null ? void 0 : _g2.size) || "cover", 377 backgroundPosition: ((_h2 = bg.image) == null ? void 0 : _h2.position) || "center", 378 backgroundRepeat: ((_i2 = bg.image) == null ? void 0 : _i2.repeat) || "no-repeat", 379 filter: ((_j2 = bg.image) == null ? void 0 : _j2.blur) ? `blur(${bg.image.blur}px)` : void 0, 380 transform: `scale(${scale}) translate(${offsetX}%, ${offsetY}%)`, 381 transformOrigin: stageCalib.origin || "50% 50%", 382 transition: isDraggingStage ? "none" : "transform 0.3s cubic-bezier(0.2, 0.8, 0.2, 1), background-image 0.3s ease" 383 }; 384 } 385 }, [activeConfig, isDraggingStage]); 386 const activeStageCalib = reactExports.useMemo(() => { 387 var _a2; 388 return getSouthparkStageAlignment((_a2 = activeConfig.image) == null ? void 0 : _a2.url); 389 }, [(_a = activeConfig.image) == null ? void 0 : _a.url]); 390 const effectiveScale = ((_b = activeConfig.image) == null ? void 0 : _b.scale) !== void 0 ? activeConfig.image.scale : activeStageCalib.scale; 391 const effectiveOffsetX = ((_c = activeConfig.image) == null ? void 0 : _c.offsetX) !== void 0 ? activeConfig.image.offsetX : activeStageCalib.offsetX || 0; 392 const effectiveOffsetY = ((_d = activeConfig.image) == null ? void 0 : _d.offsetY) !== void 0 ? activeConfig.image.offsetY : activeStageCalib.offsetY; 393 const effectiveBackY = ((_e = activeConfig.image) == null ? void 0 : _e.backY) !== void 0 ? activeConfig.image.backY : activeStageCalib.backY; 394 const effectiveFrontY = ((_f = activeConfig.image) == null ? void 0 : _f.frontY) !== void 0 ? activeConfig.image.frontY : activeStageCalib.frontY; 395 const effectiveBackScale = ((_g = activeConfig.image) == null ? void 0 : _g.backScale) !== void 0 ? activeConfig.image.backScale : activeStageCalib.backScale; 396 const effectiveFrontScale = ((_h = activeConfig.image) == null ? void 0 : _h.frontScale) !== void 0 ? activeConfig.image.frontScale : activeStageCalib.frontScale; 397 const effectiveHorizonY = ((_i = activeConfig.image) == null ? void 0 : _i.horizonY) !== void 0 ? activeConfig.image.horizonY : activeStageCalib.horizonY; 398 const DEPTH_PRESETS = [ 399 { label: "Dramatic 3D", bScale: 0.52, fScale: 1 }, 400 { label: "Natural Amphitheater", bScale: 0.58, fScale: 0.98 }, 401 { label: "Subtle Depth", bScale: 0.72, fScale: 0.98 }, 402 { label: "Flat", bScale: 0.9, fScale: 1 } 403 ]; 404 const applyDepthPreset = (bScale, fScale) => { 405 updateActiveConfig((prev) => ({ 406 ...prev, 407 image: { 408 ...prev.image || DEFAULT_STAGE_IMAGE, 409 backScale: bScale, 410 frontScale: fScale 411 } 412 })); 413 }; 414 const resetDepthToDefaults = () => { 415 var _a2; 416 const defaultCalib = getSouthparkStageAlignment((_a2 = activeConfig.image) == null ? void 0 : _a2.url); 417 updateActiveConfig((prev) => ({ 418 ...prev, 419 image: { 420 ...prev.image || DEFAULT_STAGE_IMAGE, 421 backY: defaultCalib.backY, 422 frontY: defaultCalib.frontY, 423 backScale: defaultCalib.backScale, 424 frontScale: defaultCalib.frontScale, 425 horizonY: defaultCalib.horizonY 426 } 427 })); 428 }; 429 const resetStageAlignment = () => { 430 var _a2; 431 const defaultCalib = getSouthparkStageAlignment((_a2 = activeConfig.image) == null ? void 0 : _a2.url); 432 updateActiveConfig((prev) => ({ 433 ...prev, 434 image: { 435 ...prev.image || DEFAULT_STAGE_IMAGE, 436 scale: defaultCalib.scale, 437 offsetX: defaultCalib.offsetX || 0, 438 offsetY: defaultCalib.offsetY 439 } 440 })); 441 }; 442 const handleDepthLinePointerDown = (type, e) => { 443 e.stopPropagation(); 444 try { 445 e.currentTarget.setPointerCapture(e.pointerId); 446 } catch { 447 } 448 setActiveDepthDrag(type); 449 }; 450 const handleDepthLinePointerMove = (type, e) => { 451 if (activeDepthDrag !== type) return; 452 e.stopPropagation();
453 if (!svgViewportRef.current) return; 454 const rect = svgViewportRef.current.getBoundingClientRect(); 455 if (!rect.height) return; 456 const relativeY = Math.round((e.clientY - rect.top) / rect.height * 650); 457 if (type === "backY") { 458 const minBound = 240; 459 const maxBound = effectiveFrontY - 30; 460 const clamped = Math.max(minBound, Math.min(maxBound, relativeY)); 461 updateActiveConfig((prev) => ({ 462 ...prev, 463 image: { ...prev.image || DEFAULT_STAGE_IMAGE, backY: clamped } 464 })); 465 } else if (type === "frontY") { 466 const minBound = effectiveBackY + 30; 467 const clamped = Math.max(minBound, Math.min(505, relativeY)); 468 updateActiveConfig((prev) => ({ 469 ...prev, 470 image: { ...prev.image || DEFAULT_STAGE_IMAGE, frontY: clamped } 471 })); 472 } 473 }; 474 const handleDepthLinePointerUp = (e) => { 475 e.stopPropagation(); 476 try { 477 e.currentTarget.releasePointerCapture(e.pointerId); 478 } catch { 479 } 480 setActiveDepthDrag(null); 481 }; 482 const handleStagePointerDown = (e) => { 483 if (e.button !== 0 || activeDepthDrag) return; 484 const target = e.currentTarget; 485 try { 486 target.setPointerCapture(e.pointerId); 487 } catch { 488 } 489 const rect = target.getBoundingClientRect(); 490 dragStartRef.current = { 491 clientX: e.clientX, 492 clientY: e.clientY, 493 startOffsetX: effectiveOffsetX, 494 startOffsetY: effectiveOffsetY, 495 width: rect.width || 1, 496 height: rect.height || 1 497 }; 498 setIsDraggingStage(true); 499 }; 500 const handleStagePointerMove = (e) => { 501 if (!isDraggingStage || !dragStartRef.current) return; 502 const dx = e.clientX - dragStartRef.current.clientX; 503 const dy = e.clientY - dragStartRef.current.clientY; 504 const deltaXPercent = dx / dragStartRef.current.width * 100; 505 const deltaYPercent = dy / dragStartRef.current.height * 100; 506 const nextOffsetX = Math.max(-50, Math.min(50, Math.round(dragStartRef.current.startOffsetX + deltaXPercent))); 507 const nextOffsetY = Math.max(-40, Math.min(50, Math.round(dragStartRef.current.startOffsetY + deltaYPercent))); 508 updateActiveConfig((prev) => ({ 509 ...prev, 510 image: { 511 ...prev.image || DEFAULT_STAGE_IMAGE, 512 offsetX: nextOffsetX, 513 offsetY: nextOffsetY 514 } 515 })); 516 }; 517 const handleStagePointerUp = (e) => { 518 if (isDraggingStage) { 519 setIsDraggingStage(false); 520 dragStartRef.current = null; 521 try { 522 e.currentTarget.releasePointerCapture(e.pointerId); 523 } catch { 524 } 525 } 526 }; 527 const handleStageWheel = (e) => { 528 e.preventDefault(); 529 const zoomDelta = e.deltaY < 0 ? 0.04 : -0.04; 530 const nextScale = Math.min(2.5, Math.max(1, Number((effectiveScale + zoomDelta).toFixed(2)))); 531 updateActiveConfig((prev) => ({ 532 ...prev, 533 image: { 534 ...prev.image || DEFAULT_STAGE_IMAGE, 535 scale: nextScale 536 } 537 })); 538 }; 539 const effectiveStageConfig = reactExports.useMemo(() => ({ 540 ...stageConfig, 541 backY: effectiveBackY, 542 frontY: effectiveFrontY, 543 backScale: effectiveBackScale, 544 frontScale: effectiveFrontScale, 545 horizonY: effectiveHorizonY 546 }), [stageConfig, effectiveBackY, effectiveFrontY, effectiveBackScale, effectiveFrontScale, effectiveHorizonY]); 547 const sampleMusicians = reactExports.useMemo(() => { 548 const activeEnsemble = SAMPLE_ENSEMBLE_POOL.slice(0, Math.max(1, Math.min(24, sampleMusicianCount))); 549 const placed = calculateEnsemblePlacement(activeEnsemble, { 550 stageConfig: effectiveStageConfig, 551 enableDensityScaling, 552 stageWidth: 1200, 553 stageHeight: 650 554 }); 555 return placed.map((p) => { 556 var _a2; 557 return { 558 id: p.m.id, 559 animalId: p.m.animalId, 560 program: p.m.program, 561 isPercussion: p.m.isPercussion, 562 percRole: p.m.percRole, 563 x: p.posX, 564 y: p.posY, 565 scale: p.musicianScale, 566 tier: `${p.rowName === "back" ? "Back" : p.rowName === "middle" ? "Middle" : "Front"} · ${p.m.tier ? ((_a2 = p.m.tier.split("·")[1]) == null ? void 0 : _a2.trim()) || p.m.id : p.m.id}` 567 }; 568 }); 569 }, [effectiveStageConfig, sampleMusicianCount, enableDensityScaling]); 570 const filteredPresets = reactExports.useMemo(() => { 571 return mergedPresets.filter((p) => {
572 if (galleryFilter === "sp_overrides" && !p.isSpOverride) return false; 573 if (galleryFilter === "shared" && p.isSpOverride) return false; 574 if (searchQuery.trim()) { 575 const q = searchQuery.toLowerCase().trim(); 576 const matchName = p.name.toLowerCase().includes(q); 577 const matchTag = p.tags.some((t) => t.toLowerCase().includes(q)); 578 if (!matchName && !matchTag) return false; 579 } 580 return true; 581 }); 582 }, [mergedPresets, galleryFilter, searchQuery]); 583 const activeParticleMeta = PARTICLE_OPTIONS.find((p) => { 584 var _a2; 585 return p.id === ((_a2 = activeConfig.particles) == null ? void 0 : _a2.type); 586 }) || PARTICLE_OPTIONS[0]; 587 const activeLightingProfile = resolveLightingProfile(activeConfig, activePresetMeta.tags); 588 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-8 animate-fadeIn", children: [ 589 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "bg-gradient-to-r from-zinc-900 via-indigo-950/40 to-zinc-900 border border-indigo-500/30 rounded-2xl p-5 shadow-2xl relative overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col lg:flex-row items-start lg:items-center justify-between gap-4 relative z-10", children: [
590 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4", children: [ 591 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-16 h-12 rounded-lg border border-indigo-400/40 overflow-hidden bg-black shadow-md shrink-0 relative", children: [ 592 activeConfig.type === "image" && ((_j = activeConfig.image) == null ? void 0 : _j.url) ? /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: activeConfig.image.url, alt: "Active preview", className: "w-full h-full object-cover" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-full h-full", style: stageBackgroundStyle }), 593 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "absolute bottom-0 right-0 bg-black/80 text-[10px] px-1 rounded-tl text-zinc-300", children: activeParticleMeta.icon }) 594 ] }), 595 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 596 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2.5 flex-wrap", children: [
597 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-lg font-bold text-white flex items-center gap-2", children: activePresetMeta.name }), 598 isCurrentSelectedSpOverride ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "px-2.5 py-0.5 rounded-full text-[11px] font-bold bg-amber-500/20 text-amber-300 border border-amber-500/30 flex items-center gap-1", children: [ 599 /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { size: 12 }), 600 " South Park Custom Override" 601 ] }) : selectedPresetId === null ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "px-2.5 py-0.5 rounded-full text-[11px] font-bold bg-purple-500/20 text-purple-300 border border-purple-500/30 flex items-center gap-1", children: "ð² Dynamic Shuffle (Untagged MIDIs)" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "px-2.5 py-0.5 rounded-full text-[11px] font-bold bg-blue-500/20 text-blue-300 border border-blue-500/30 flex items-center gap-1", children: "ð» Shared Orchestra Preset" }) 602 ] }),
603 /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-zinc-400 mt-1 flex items-center gap-2 flex-wrap", children: [ 604 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [ 605 "Lighting: ", 606 /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { className: "text-amber-400 font-semibold", children: activeLightingProfile.name }) 607 ] }), 608 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-600", children: "â¢" }), 609 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [ 610 "Particles: ", 611 /* @__PURE__ */ jsxRuntimeExports.jsxs("strong", { className: "text-zinc-200", children: [ 612 activeParticleMeta.label, 613 " (", 614 ((_k = activeConfig.particles) == null ? void 0 : _k.count) ?? 50, 615 ")" 616 ] }) 617 ] }), 618 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-600", children: "â¢" }),
619 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [ 620 "Type: ", 621 /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { className: "text-zinc-200 capitalize", children: activeConfig.type }) 622 ] }), 623 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-600", children: "â¢" }), 624 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [ 625 "Tags: ", 626 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-300 font-mono text-[11px]", children: activePresetMeta.tags.join(", ") || "none" }) 627 ] }) 628 ] }) 629 ] }) 630 ] }), 631 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-2 shrink-0 self-end lg:self-center", children: isCurrentSelectedSpOverride ? /* @__PURE__ */ jsxRuntimeExports.jsxs( 632 "button", 633 { 634 onClick: handleRevertToShared, 635 className: "px-3 py-1.5 rounded-lg text-xs font-semibold bg-zinc-800 hover:bg-zinc-700 text-zinc-300 border border-zinc-700 flex items-center gap-1.5 transition-colors", 636 title: "Revert to inherited shared orchestra configuration", 637 children: [
638 /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { size: 14 }), 639 " Revert to Orchestra Preset" 640 ] 641 } 642 ) : /* @__PURE__ */ jsxRuntimeExports.jsxs( 643 "button", 644 { 645 onClick: () => { 646 updateActiveConfig((prev) => ({ ...prev })); 647 }, 648 className: "px-3 py-1.5 rounded-lg text-xs font-semibold bg-indigo-600 hover:bg-indigo-500 text-white shadow-md flex items-center gap-1.5 transition-colors", 649 title: "Create an independent South Park override for this background", 650 children: [ 651 /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { size: 14 }), 652 " Customize for South Park" 653 ] 654 } 655 ) }) 656 ] }) }), 657 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-1 xl:grid-cols-12 gap-6 items-start", children: [ 658 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "xl:col-span-7 2xl:col-span-8 bg-zinc-950 border border-zinc-800/90 rounded-2xl p-4 shadow-xl space-y-3", children: [
659 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between flex-wrap gap-3 pb-2 border-b border-zinc-800/80", children: [ 660 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 661 /* @__PURE__ */ jsxRuntimeExports.jsx(Monitor, { size: 16, className: "text-indigo-400" }), 662 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm font-bold text-white tracking-wide", children: "Live Stage Composite Canvas" }), 663 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-zinc-500", children: "(1200 x 650 Virtual Stage)" }) 664 ] }), 665 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [ 666 /* @__PURE__ */ jsxRuntimeExports.jsxs( 667 "button", 668 { 669 onClick: () => setIsParticlesPlaying(!isParticlesPlaying), 670 className: `px-2.5 py-1 rounded-md text-xs font-medium border flex items-center gap-1.5 transition-colors ${isParticlesPlaying ? "bg-indigo-500/20 text-indigo-300 border-indigo-500/40" : "bg-zinc-900 text-zinc-400 border-zinc-800 hover:text-white"}`, 671 children: [
672 isParticlesPlaying ? /* @__PURE__ */ jsxRuntimeExports.jsx(Pause, { size: 13 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Play, { size: 13 }), 673 isParticlesPlaying ? "Particles Active" : "Particles Paused" 674 ] 675 } 676 ), 677 /* @__PURE__ */ jsxRuntimeExports.jsxs( 678 "button", 679 { 680 onClick: () => setShowSampleMusicians(!showSampleMusicians), 681 className: `px-2.5 py-1 rounded-md text-xs font-medium border flex items-center gap-1.5 transition-colors ${showSampleMusicians ? "bg-amber-500/20 text-amber-300 border-amber-500/40" : "bg-zinc-900 text-zinc-500 border-zinc-800"}`, 682 children: [ 683 showSampleMusicians ? /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { size: 13 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(EyeOff, { size: 13 }), 684 showSampleMusicians ? "Musicians Visible" : "Musicians Hidden" 685 ] 686 } 687 ), 688 /* @__P
688URE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 bg-zinc-900 px-2 py-1 rounded-md border border-zinc-800 text-xs", children: [ 689 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-400 font-medium", children: "Musicians:" }), 690 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono font-bold text-amber-400", children: sampleMusicianCount }), 691 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-0.5 ml-1", children: [1, 3, 5, 8, 12, 18, 24].map((num) => /* @__PURE__ */ jsxRuntimeExports.jsx( 692 "button", 693 { 694 type: "button", 695 onClick: () => setSampleMusicianCount(num), 696 className: `px-1.5 py-0.5 rounded text-[10px] font-semibold transition-all ${sampleMusicianCount === num ? "bg-amber-500 text-black shadow-sm" : "text-zinc-400 hover:text-white bg-zinc-800 hover:bg-zinc-700"}`, 697 children: num 698 }, 699 num 700 )) }) 701 ] }),
702 /* @__PURE__ */ jsxRuntimeExports.jsxs( 703 "button", 704 { 705 onClick: () => setShowArcGuides(!showArcGuides), 706 className: `px-2.5 py-1 rounded-md text-xs font-medium border flex items-center gap-1.5 transition-colors ${showArcGuides ? "bg-indigo-500/20 text-indigo-300 border-indigo-500/40" : "bg-zinc-900 text-zinc-500 border-zinc-800 hover:text-white"}`, 707 children: [ 708 /* @__PURE__ */ jsxRuntimeExports.jsx(Compass, { size: 13 }), 709 " Arc Guides" 710 ] 711 } 712 ), 713 /* @__PURE__ */ jsxRuntimeExports.jsxs( 714 "button", 715 { 716 onClick: () => setShowDepthGuides(!showDepthGuides), 717 className: `px-2.5 py-1 rounded-md text-xs font-medium border flex items-center gap-1.5 transition-colors ${showDepthGuides ? "bg-cyan-500/20 text-cyan-300 border-cyan-500/40" : "bg-zinc-900 text-zinc-500 border-zinc-800 hover:text-white"}`, 718 children: [
719 /* @__PURE__ */ jsxRuntimeExports.jsx(Layers, { size: 13 }), 720 " 3D Depth Lines" 721 ] 722 } 723 ), 724 /* @__PURE__ */ jsxRuntimeExports.jsxs( 725 "button", 726 { 727 onClick: () => setPreviewPhysics((p) => p === "snappy" ? "smooth" : "snappy"), 728 className: "px-2.5 py-1 rounded-md text-xs font-medium bg-zinc-900 hover:bg-zinc-800 text-zinc-300 border border-zinc-800 transition-colors capitalize", 729 children: [ 730 "Physics: ", 731 previewPhysics 732 ] 733 } 734 ), 735 /* @__PURE__ */ jsxRuntimeExports.jsxs( 736 "button", 737 { 738 onClick: () => { 739 const modes = ["dynamic", "subtle", "off"]; 740 const next = modes[(modes.indexOf(previewLighting) + 1) % modes.length]; 741 setPreviewLighting(next); 742 }, 743 className: `px-2.5 py-1 rounded-md text-xs font-medium border flex items-center gap-1.5 transition-colors ${previewLighting !== "off" ? "bg-amber-500/20 text-amber-300 border-amber-500/40" : "bg-zinc-900 text-zinc-500 border-zinc-800 hover:text-white"}`, 744 title: "Toggle preview stage lighting effects", 745 children: [
746 /* @__PURE__ */ jsxRuntimeExports.jsx(Lightbulb, { size: 13 }), 747 " Lighting: ", 748 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "capitalize", children: previewLighting }) 749 ] 750 } 751 ), 752 selectedPresetId === null && /* @__PURE__ */ jsxRuntimeExports.jsxs( 753 "button", 754 { 755 onClick: () => setShufflePreviewIndex((prev) => (prev + 1) % Math.max(1, mergedPresets.length)), 756 className: "px-2.5 py-1 rounded-md text-xs font-semibold bg-purple-600/30 text-purple-200 border border-purple-500/40 hover:bg-purple-600/50 flex items-center gap-1.5 transition-colors shadow-sm", 757 title: "Cycle random stage preview on the canvas", 758 children: [ 759 /* @__PURE__ */ jsxRuntimeExports.jsx(Shuffle, { size: 13 }), 760 " Shuffle Stage" 761 ] 762 } 763 ) 764 ] }) 765 ] }), 766 /* @__PURE__ */ jsxRuntimeExports.jsxs( 767 "div", 768 { 769 className: `w-full relative rounded-xl overflow-hidden border border-zinc-800 shadow-inner select-none touch-none ${isDraggingStage ? "cursor-grabbing" : "cursor-grab"}`, 770 style: { aspectRatio: "1200 / 650", minHeight: "380px" }, 771 onPointerDown: handleStagePointerDown, 772 onPointerMove: handleStagePointerMove, 773 onPointerUp: handleStagePointerUp, 774 onPointerCancel: handleStagePointerUp, 775 onWheel: handleStageWheel, 776 children: [
777 /* @__PURE__ */ jsxRuntimeExports.jsx( 778 "div", 779 { 780 className: "absolute inset-0 pointer-events-none", 781 style: stageBackgroundStyle 782 } 783 ), 784 ((_l = activeConfig.overlay) == null ? void 0 : _l.enabled) && /* @__PURE__ */ jsxRuntimeExports.jsx( 785 "div", 786 { 787 className: "absolute inset-0 pointer-events-none transition-opacity duration-300", 788 style: { 789 backgroundColor: activeConfig.overlay.color || "#000000", 790 opacity: activeConfig.overlay.opacity ?? 0.15 791 } 792 } 793 ), 794 ((_m = activeConfig.particles) == null ? void 0 : _m.enabled) && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 pointer-events-none z-10", children: /* @__PURE__ */ jsxRuntimeExports.jsx( 795 BackgroundParticlesCanvas, 796 { 797 config: activeConfig.particles, 798 isPlaying: isParticlesPlaying 799 } 800 ) }),
801 /* @__PURE__ */ jsxRuntimeExports.jsx( 802 "div", 803 { 804 className: "absolute inset-0 pointer-events-none z-20", 805 style: { 806 background: "linear-gradient(to top, rgba(0,0,0,0.85) 0%, rgba(0,0,0,0.2) 40%, transparent 100%)" 807 } 808 } 809 ), 810 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute bottom-3 left-3 z-40 flex items-center gap-2 bg-black/75 backdrop-blur-md px-3 py-1.5 rounded-full border border-white/10 text-[11px] shadow-lg pointer-events-none transition-opacity duration-200", children: [ 811 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1.5 text-amber-300 font-medium", children: [ 812 /* @__PURE__ */ jsxRuntimeExports.jsx(Move, { size: 12, className: isDraggingStage ? "animate-pulse text-amber-400" : "" }), 813 isDraggingStage ? "Repositioning Stage..." : "Drag stage to pan" 814 ] }),
815 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-600", children: "â¢" }), 816 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-400", children: "Scroll to zoom" }), 817 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-600", children: "â¢" }), 818 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-zinc-300 text-[10px]", children: [ 819 "X: ", 820 /* @__PURE__ */ jsxRuntimeExports.jsxs("strong", { className: "text-amber-400", children: [ 821 effectiveOffsetX > 0 ? `+${effectiveOffsetX}` : effectiveOffsetX, 822 "%" 823 ] }), 824 ", Y: ", 825 /* @__PURE__ */ jsxRuntimeExports.jsxs("strong", { className: "text-amber-400", children: [ 826 effectiveOffsetY > 0 ? `+${effectiveOffsetY}` : effectiveOffsetY, 827 "%" 828 ] }), 829 ", Zoom: ",
830 /* @__PURE__ */ jsxRuntimeExports.jsxs("strong", { className: "text-amber-400", children: [ 831 effectiveScale.toFixed(2), 832 "x" 833 ] }) 834 ] }) 835 ] }), 836 /* @__PURE__ */ jsxRuntimeExports.jsxs( 837 "svg", 838 { 839 ref: svgViewportRef, 840 className: "absolute inset-0 w-full h-full z-30 pointer-events-none", 841 viewBox: "0 0 1200 650", 842 preserveAspectRatio: "xMidYMid meet", 843 children: [ 844 /* @__PURE__ */ jsxRuntimeExports.jsxs("defs", { children: [ 845 /* @__PURE__ */ jsxRuntimeExports.jsxs("radialGradient", { id: "shadowGrad", cx: "50%", cy: "50%", r: "50%", children: [ 846 /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "0%", stopColor: "#000000", stopOpacity: "0.65" }), 847 /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "45%", stopColor: "#000000", stopOpacity: "0.30" }),
848 /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "75%", stopColor: "#000000", stopOpacity: "0.10" }), 849 /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "92%", stopColor: "#000000", stopOpacity: "0.02" }), 850 /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "100%", stopColor: "#000000", stopOpacity: "0" }) 851 ] }), 852 /* @__PURE__ */ jsxRuntimeExports.jsxs("linearGradient", { id: "arcLineGrad", x1: "0%", y1: "0%", x2: "100%", y2: "0%", children: [ 853 /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "0%", stopColor: "#818cf8", stopOpacity: "0.2" }), 854 /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "50%", stopColor: "#818cf8", stopOpacity: "0.9" }), 855 /* @__PURE__ */ jsxRuntimeExports.jsx("stop", { offset: "100%", stopColor: "#818cf8", stopOpacity: "0.2" }) 856 ] }) 857 ] }), 858 showArcGuides && (() => { 859 const yOff = stageConfig.yOffset ?? 0; 860 const arcStr = stageConfig.arcFactor ?? 1; 861 return /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { className: "arc-guides", stroke: "url(#arcLineGrad)", strokeWidth: "2", strokeDasharray: "6 4", fill: "none", children: [
862 /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: `M 180 ${390 + yOff} Q 600 ${390 + yOff - 25 * arcStr} 1020 ${390 + yOff}` }), 863 /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: `M 180 ${465 + yOff} Q 600 ${465 + yOff - 25 * arcStr} 1020 ${465 + yOff}` }), 864 /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: `M 180 ${535 + yOff} Q 600 ${535 + yOff - 25 * arcStr} 1020 ${535 + yOff}` }), 865 /* @__PURE__ */ jsxRuntimeExports.jsxs("text", { x: "600", y: 370 + yOff, fill: "#a5b4fc", fontSize: "12", textAnchor: "middle", fontWeight: "bold", children: [ 866 "Seating Arc (Curvature: ", 867 arcStr.toFixed(2), 868 "x)" 869 ] }) 870 ] }); 871 })(), 872 showDepthGuides && (() => { 873 const bY = effectiveBackY; 874 const fY = effectiveFrontY; 875 const mY = Math.round((bY + fY) / 2); 876 return /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { className: "depth-guides", children: [
877 /* @__PURE__ */ jsxRuntimeExports.jsx( 878 "polygon", 879 { 880 points: `160,${bY} 1040,${bY} 1120,${fY} 80,${fY}`, 881 fill: "rgba(14, 165, 233, 0.05)", 882 stroke: "rgba(14, 165, 233, 0.3)", 883 strokeWidth: "1.5", 884 strokeDasharray: "4 4" 885 } 886 ), 887 /* @__PURE__ */ jsxRuntimeExports.jsx( 888 "line", 889 { 890 x1: "110", 891 y1: mY, 892 x2: "1090", 893 y2: mY, 894 stroke: "#818cf8", 895 strokeWidth: "1", 896 strokeDasharray: "3 3", 897 opacity: "0.6" 898 } 899 ), 900 /* @__P
900URE__ */ jsxRuntimeExports.jsxs("text", { x: "180", y: mY - 5, fill: "#a5b4fc", fontSize: "10", opacity: "0.8", children: [ 901 "Mid Tier Reference (Y: ", 902 mY, 903 ")" 904 ] }), 905 /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { className: "back-ground-guide-group", children: [ 906 /* @__PURE__ */ jsxRuntimeExports.jsx( 907 "rect", 908 { 909 x: "120", 910 y: bY - 14, 911 width: "960", 912 height: "28", 913 fill: "transparent", 914 className: "cursor-ns-resize pointer-events-auto", 915 onPointerDown: (e) => handleDepthLinePointerDown("backY", e), 916 onPointerMove: (e) => handleDepthLinePointerMove("backY", e), 917 onPointerUp: handleDepthLinePointerUp, 918 onPointerCancel: handleDepthLinePointerUp 919 } 920 ),
921 /* @__PURE__ */ jsxRuntimeExports.jsx( 922 "line", 923 { 924 x1: "140", 925 y1: bY, 926 x2: "1060", 927 y2: bY, 928 stroke: "#38bdf8", 929 strokeWidth: activeDepthDrag === "backY" ? 4 : 2.5, 930 strokeDasharray: "6 4" 931 } 932 ), 933 /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "140", cy: bY, r: activeDepthDrag === "backY" ? 7 : 5, fill: "#38bdf8", stroke: "#0284c7", strokeWidth: "2" }), 934 /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "1060", cy: bY, r: activeDepthDrag === "backY" ? 7 : 5, fill: "#38bdf8", stroke: "#0284c7", strokeWidth: "2" }), 935 /* @__PURE__ */ jsxRuntimeExports.jsxs( 936 "g", 937 { 938 className: "cursor-ns-resize pointer-events-auto", 939 onPointerDown: (e) => handleDepthLinePointerDown("backY", e), 940 onPointerMove: (e) => handleDepthLinePointerMove("backY", e), 941 onPointerUp: handleDepthLinePointerUp, 942 onPointerCancel: handleDepthLinePointerUp, 943 children: [
944 /* @__PURE__ */ jsxRuntimeExports.jsx( 945 "rect", 946 { 947 x: "410", 948 y: bY - 14, 949 width: "380", 950 height: "28", 951 rx: "14", 952 fill: activeDepthDrag === "backY" ? "#0369a1" : "rgba(24, 24, 27, 0.92)", 953 stroke: "#38bdf8", 954 strokeWidth: activeDepthDrag === "backY" ? 2 : 1.5, 955 className: "transition-colors filter drop-shadow-md" 956 } 957 ), 958 /* @__PURE__ */ jsxRuntimeExports.jsxs("text", { x: "600", y: bY + 4, fill: activeDepthDrag === "backY" ? "#ffffff" : "#38bdf8", fontSize: "11", textAnchor: "middle", fontWeight: "bold", children: [ 959 "â BACK ROW GROUND (Y: ", 960 bY, 961 ", Scale: ", 962 effectiveBackScale.toFixed(2), 963 "x)" 964 ] }) 965 ] 966 } 967 ) 968 ] }),
969 /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { className: "front-ground-guide-group", children: [ 970 /* @__PURE__ */ jsxRuntimeExports.jsx( 971 "rect", 972 { 973 x: "50", 974 y: fY - 14, 975 width: "1100", 976 height: "28", 977 fill: "transparent", 978 className: "cursor-ns-resize pointer-events-auto", 979 onPointerDown: (e) => handleDepthLinePointerDown("frontY", e), 980 onPointerMove: (e) => handleDepthLinePointerMove("frontY", e), 981 onPointerUp: handleDepthLinePointerUp, 982 onPointerCancel: handleDepthLinePointerUp 983 } 984 ), 985 /* @__PURE__ */ jsxRuntimeExports.jsx( 986 "line", 987 { 988 x1: "60", 989 y1: fY, 990 x2: "1140", 991 y2: fY, 992 stroke: "#10b981", 993 strokeWidth: activeDepthDrag === "frontY" ? 4 : 2.5, 994 strokeDasharray: "6 4" 995 } 996 ),
997 /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "60", cy: fY, r: activeDepthDrag === "frontY" ? 7 : 5, fill: "#10b981", stroke: "#059669", strokeWidth: "2" }), 998 /* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: "1140", cy: fY, r: activeDepthDrag === "frontY" ? 7 : 5, fill: "#10b981", stroke: "#059669", strokeWidth: "2" }), 999 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1000 "g", 1001 { 1002 className: "cursor-ns-resize pointer-events-auto", 1003 onPointerDown: (e) => handleDepthLinePointerDown("frontY", e), 1004 onPointerMove: (e) => handleDepthLinePointerMove("frontY", e), 1005 onPointerUp: handleDepthLinePointerUp, 1006 onPointerCancel: handleDepthLinePointerUp, 1007 children: [ 1008 /* @__PURE__ */ jsxRuntimeExports.jsx( 1009 "rect", 1010 { 1011 x: "400", 1012 y: fY - 14, 1013 width: "400", 1014 height: "28", 1015 rx: "14", 1016 fill: activeDepthDrag === "frontY" ? "#047857" : "rgba(24, 24, 27, 0.92)", 1017 stroke: "#10b981", 1018 strokeWidth: activeDepthDrag === "frontY" ? 2 : 1.5, 1019 className: "transition-colors filter drop-shadow-md" 1020 } 1021 ),
1022 /* @__PURE__ */ jsxRuntimeExports.jsxs("text", { x: "600", y: fY + 4, fill: activeDepthDrag === "frontY" ? "#ffffff" : "#34d399", fontSize: "11", textAnchor: "middle", fontWeight: "bold", children: [ 1023 "â FRONT ROW GROUND (Y: ", 1024 fY, 1025 ", Scale: ", 1026 effectiveFrontScale.toFixed(2), 1027 "x)" 1028 ] }) 1029 ] 1030 } 1031 ) 1032 ] }) 1033 ] }); 1034 })(), 1035 /* @__PURE__ */ jsxRuntimeExports.jsx( 1036 SouthparkStageBanner, 1037 { 1038 track: { 1039 id: "designer-preview", 1040 title: (activePresetMeta == null ? void 0 : activePresetMeta.name) || "HamieNET Orchestra", 1041 artist: "South Park Stage" 1042 }, 1043 backgroundUrl: (_n = activeConfig.image) == null ? void 0 : _n.url, 1044 currentTime: isParticlesPlaying ? Date.now() / 1e3 : 0, 1045 bpm: 120, 1046 isPlaying: isParticlesPlaying, 1047 globalIntensity: 0.65, 1048 showBanner: true 1049 } 1050 ),
1051 /* @__PURE__ */ jsxRuntimeExports.jsx( 1052 SouthparkStageLighting, 1053 { 1054 currentTime: isParticlesPlaying ? Date.now() / 1e3 : 0, 1055 isPlaying: isParticlesPlaying, 1056 bpm: 120, 1057 bps: 2, 1058 backgroundConfig: activeConfig, 1059 lightingMode: previewLighting, 1060 musicians: sampleMusicians.map((m, idx) => ({ 1061 uniqueName: m.id, 1062 baseName: m.tier, 1063 index: idx, 1064 program: m.program, 1065 isPercussion: m.isPercussion 1066 })), 1067 musicianPositions: sampleMusicians.reduce((acc, m) => { 1068 const colors = ["#ef4444", "#10b981", "#a855f7", "#eab308", "#06b6d4", "#f97316"]; 1069 acc[m.id] = { 1070 posX: m.x, 1071 posY: m.y, 1072 scale: m.scale, 1073 trackColor: colors[m.id.charCodeAt(0) % colors.length], 1074 isPercussion: m.isPercussion 1075 }; 1076 return acc; 1077 }, {}), 1078 activeNotes: sampleMusicians.reduce((acc, m, idx) => { 1079 if (idx < Math.min(3, sampleMusicians.length)) { 1080 acc[m.id] = { velocity: 105, pitch: 64 }; 1081 } 1082 return acc; 1083 }, {}), 1084 soloedIndex: null, 1085 selectedTrackIndex: null, 1086 globalIntensity: 0.65, 1087 stageWidth: 1200, 1088 stageHeight: 650 1089 } 1090 ), 1091 showSampleMusicians && sampleMusicians.map((m) => { 1092 const animal = BASE_ANIMALS[m.animalId] || BASE_ANIMALS["fox"]; 1093 const inst = getInstrumentDef(m.program, m.isPercussion, m.percRole); 1094 const customCfg = charactersConfig == null ? void 0 : charactersConfig[m.animalId]; 1095 return /* @__PURE__ */ jsxRuntimeExports.jsxs( 1096 "g", 1097 { 1098 transform: `translate(${m.x}, ${m.y})`, 1099 className: "transition-transform duration-200", 1100 children: [
1101 /* @__PURE__ */ jsxRuntimeExports.jsx( 1102 "ellipse", 1103 { 1104 cx: "0", 1105 cy: 78 * m.scale, 1106 rx: 44 * m.scale, 1107 ry: 12 * m.scale, 1108 fill: "url(#shadowGrad)" 1109 } 1110 ), 1111 /* @__PURE__ */ jsxRuntimeExports.jsx("g", { transform: `scale(${m.scale})`, children: /* @__PURE__ */ jsxRuntimeExports.jsx( 1112 SouthparkCharacter, 1113 { 1114 animal, 1115 instrument: inst, 1116 isActive: isParticlesPlaying, 1117 velocity: 100, 1118 physicsMode: previewPhysics, 1119 customConfig: customCfg, 1120 costumes: costumesConfig ? [] : [] 1121 } 1122 ) }),
1123 /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { transform: `translate(0, ${70 * m.scale + 8})`, className: "pointer-events-none select-none", children: [ 1124 /* @__PURE__ */ jsxRuntimeExports.jsx( 1125 "rect", 1126 { 1127 x: "-45", 1128 y: "0", 1129 width: "90", 1130 height: "20", 1131 rx: "10", 1132 fill: "rgba(9, 9, 11, 0.9)", 1133 stroke: "rgba(168, 85, 247, 0.6)", 1134 strokeWidth: "1.2" 1135 } 1136 ), 1137 /* @__PURE__ */ jsxRuntimeExports.jsx( 1138 "text", 1139 { 1140 x: "0", 1141 y: "13", 1142 fill: "#e4e4e7", 1143 fontSize: "9", 1144 textAnchor: "middle", 1145 fontWeight: "bold", 1146 className: "font-mono", 1147 children: m.tier 1148 } 1149 ) 1150 ] }) 1151 ] 1152 }, 1153 m.id 1154 ); 1155 }) 1156 ] 1157 } 1158 ) 1159 ] 1160 } 1161 ) 1162 ] }),
1163 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "xl:col-span-5 2xl:col-span-4 space-y-3 sticky top-4 max-h-[calc(100vh-5rem)] overflow-y-auto pr-1 custom-scrollbar", children: [ 1164 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-900/80 border border-zinc-800 rounded-xl overflow-hidden shadow-lg transition-all", children: [ 1165 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1166 "button", 1167 { 1168 type: "button", 1169 onClick: () => toggleSection("depth"), 1170 className: "w-full px-4 py-3 flex items-center justify-between bg-zinc-900 hover:bg-zinc-850 transition-colors text-left select-none", 1171 children: [ 1172 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 1173 /* @__PURE__ */ jsxRuntimeExports.jsx(Layers, { size: 16, className: "text-cyan-400" }), 1174 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm font-bold text-white", children: "3D Stage Depth & Perspective" }) 1175 ] }),
1176 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 1177 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[11px] font-mono text-cyan-300 font-semibold bg-cyan-500/10 border border-cyan-500/20 px-2 py-0.5 rounded-full", children: [ 1178 "-", 1179 Math.round((1 - effectiveBackScale / effectiveFrontScale) * 100), 1180 "% depth" 1181 ] }), 1182 openSections.depth ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { size: 15, className: "text-zinc-400" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 15, className: "text-zinc-400" }) 1183 ] }) 1184 ] 1185 } 1186 ), 1187 openSections.depth && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 border-t border-zinc-800/80 space-y-4 bg-zinc-950/40", children: [ 1188 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-950/80 p-2.5 rounded-lg border border-cyan-500/20 text-xs flex items-center justify-between", children: [
1189 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-400", children: "Back Row Reduction:" }), 1190 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono font-bold text-cyan-300", children: [ 1191 "-", 1192 Math.round((1 - effectiveBackScale / effectiveFrontScale) * 100), 1193 "% (", 1194 effectiveBackScale.toFixed(2), 1195 "x vs ", 1196 effectiveFrontScale.toFixed(2), 1197 "x)" 1198 ] }) 1199 ] }), 1200 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1.5", children: [ 1201 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] font-bold text-zinc-400 uppercase tracking-wider block", children: "Perspective Presets" }), 1202 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-2 gap-1.5", children: DEPTH_PRESETS.map((preset) => { 1203 const isMatch = Math.abs(effectiveBackScale - preset.bScale) < 0.03 && Math.abs(effectiveFrontScale - preset.fScale) < 0.03; 1204 return /* @__PURE__ */ jsxRuntimeExports.jsx( 1205 "button", 1206 { 1207 type: "button", 1208 onClick: () => applyDepthPreset(preset.bScale, preset.fScale), 1209 className: `px-2 py-1.5 rounded-lg text-xs font-medium border transition-all truncate ${isMatch ? "bg-cyan-500/20 text-cyan-200 border-cyan-400 font-semibold" : "bg-zinc-900 hover:bg-zinc-800 text-zinc-300 border-zinc-800 hover:border-zinc-700"}`, 1210 children: preset.label 1211 }, 1212 preset.label 1213 ); 1214 }) }) 1215 ] }),
1216 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-3 pt-1 border-t border-zinc-800/80", children: [ 1217 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1218 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-1 text-xs", children: [ 1219 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-300 font-medium", children: "Back Scale" }), 1220 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-cyan-400 font-bold", children: [ 1221 effectiveBackScale.toFixed(2), 1222 "x" 1223 ] }) 1224 ] }), 1225 /* @__PURE__ */ jsxRuntimeExports.jsx( 1226 "input", 1227 { 1228 type: "range", 1229 min: "0.35", 1230 max: "0.90", 1231 step: "0.02", 1232 value: effectiveBackScale, 1233 onChange: (e) => { 1234 const val = parseFloat(e.target.value); 1235 updateActiveConfig((prev) => ({ 1236 ...prev, 1237 image: { ...prev.image || DEFAULT_STAGE_IMAGE, backScale: val } 1238 })); 1239 }, 1240 className: "w-full accent-cyan-500 cursor-pointer" 1241 } 1242 ) 1243 ] }),
1244 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1245 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-1 text-xs", children: [ 1246 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-300 font-medium", children: "Front Scale" }), 1247 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-emerald-400 font-bold", children: [ 1248 effectiveFrontScale.toFixed(2), 1249 "x" 1250 ] }) 1251 ] }), 1252 /* @__PURE__ */ jsxRuntimeExports.jsx( 1253 "input", 1254 { 1255 type: "range", 1256 min: "0.70", 1257 max: "1.30", 1258 step: "0.02", 1259 value: effectiveFrontScale, 1260 onChange: (e) => { 1261 const val = parseFloat(e.target.value); 1262 updateActiveConfig((prev) => ({ 1263 ...prev, 1264 image: { ...prev.image || DEFAULT_STAGE_IMAGE, frontScale: val } 1265 })); 1266 }, 1267 className: "w-full accent-emerald-500 cursor-pointer" 1268 } 1269 ) 1270 ] }) 1271 ] }),
1272 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2.5 pt-1 border-t border-zinc-800/80 text-xs", children: [ 1273 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1274 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-1", children: [ 1275 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-cyan-400 font-medium", children: "Back Row Ground Line (Y)" }), 1276 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [ 1277 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono font-bold text-zinc-200 mr-1", children: [ 1278 effectiveBackY, 1279 "px" 1280 ] }), 1281 /* @__PURE__ */ jsxRuntimeExports.jsx( 1282 "button", 1283 { 1284 type: "button", 1285 onClick: () => updateActiveConfig((prev) => ({ 1286 ...prev, 1287 image: { ...prev.image || DEFAULT_STAGE_IMAGE, backY: Math.max(240, effectiveBackY - 5) } 1288 })), 1289 className: "px-1.5 py-0.5 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 rounded text-[10px] font-mono border border-zinc-700", 1290 children: "-5" 1291 } 1292 ),
1293 /* @__PURE__ */ jsxRuntimeExports.jsx( 1294 "button", 1295 { 1296 type: "button", 1297 onClick: () => updateActiveConfig((prev) => ({ 1298 ...prev, 1299 image: { ...prev.image || DEFAULT_STAGE_IMAGE, backY: Math.min(effectiveFrontY - 30, effectiveBackY + 5) } 1300 })), 1301 className: "px-1.5 py-0.5 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 rounded text-[10px] font-mono border border-zinc-700", 1302 children: "+5" 1303 } 1304 ) 1305 ] }) 1306 ] }), 1307 /* @__PURE__ */ jsxRuntimeExports.jsx( 1308 "input", 1309 { 1310 type: "range", 1311 min: "250", 1312 max: "450", 1313 step: "5", 1314 value: effectiveBackY, 1315 onChange: (e) => { 1316 const val = parseInt(e.target.value); 1317 updateActiveConfig((prev) => ({ 1318 ...prev, 1319 image: { ...prev.image || DEFAULT_STAGE_IMAGE, backY: val } 1320 })); 1321 }, 1322 className: "w-full accent-cyan-500 cursor-pointer" 1323 } 1324 ) 1325 ] }),
1326 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1327 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-1", children: [ 1328 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-emerald-400 font-medium", children: "Front Row Ground Line (Y)" }), 1329 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [ 1330 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono font-bold text-zinc-200 mr-1", children: [ 1331 effectiveFrontY, 1332 "px" 1333 ] }), 1334 /* @__PURE__ */ jsxRuntimeExports.jsx( 1335 "button", 1336 { 1337 type: "button", 1338 onClick: () => updateActiveConfig((prev) => ({ 1339 ...prev, 1340 image: { ...prev.image || DEFAULT_STAGE_IMAGE, frontY: Math.max(effectiveBackY + 30, effectiveFrontY - 5) } 1341 })), 1342 className: "px-1.5 py-0.5 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 rounded text-[10px] font-mono border border-zinc-700", 1343 children: "-5" 1344 } 1345 ),
1346 /* @__PURE__ */ jsxRuntimeExports.jsx( 1347 "button", 1348 { 1349 type: "button", 1350 onClick: () => updateActiveConfig((prev) => ({ 1351 ...prev, 1352 image: { ...prev.image || DEFAULT_STAGE_IMAGE, frontY: Math.min(620, effectiveFrontY + 5) } 1353 })), 1354 className: "px-1.5 py-0.5 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 rounded text-[10px] font-mono border border-zinc-700", 1355 children: "+5" 1356 } 1357 ) 1358 ] }) 1359 ] }), 1360 /* @__PURE__ */ jsxRuntimeExports.jsx( 1361 "input", 1362 { 1363 type: "range", 1364 min: "400", 1365 max: "600", 1366 step: "5", 1367 value: effectiveFrontY, 1368 onChange: (e) => { 1369 const val = parseInt(e.target.value); 1370 updateActiveConfig((prev) => ({ 1371 ...prev, 1372 image: { ...prev.image || DEFAULT_STAGE_IMAGE, frontY: val } 1373 })); 1374 }, 1375 className: "w-full accent-emerald-500 cursor-pointer" 1376 } 1377 ) 1378 ] }) 1379 ] }),
1380 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pt-2 border-t border-zinc-800/80 flex items-center justify-between text-xs", children: [ 1381 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1382 "button", 1383 { 1384 type: "button", 1385 onClick: resetDepthToDefaults, 1386 className: "text-zinc-400 hover:text-cyan-300 flex items-center gap-1 transition-colors underline", 1387 children: [ 1388 /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { size: 12 }), 1389 " Reset Depth" 1390 ] 1391 } 1392 ), 1393 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] text-zinc-500", children: "Drag lines directly on stage â" }) 1394 ] }) 1395 ] }) 1396 ] }), 1397 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-900/80 border border-zinc-800 rounded-xl overflow-hidden shadow-lg transition-all", children: [
1398 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1399 "button", 1400 { 1401 type: "button", 1402 onClick: () => toggleSection("camera"), 1403 className: "w-full px-4 py-3 flex items-center justify-between bg-zinc-900 hover:bg-zinc-850 transition-colors text-left select-none", 1404 children: [ 1405 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 1406 /* @__PURE__ */ jsxRuntimeExports.jsx(Compass, { size: 16, className: "text-amber-400" }), 1407 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm font-bold text-white", children: "Stage Camera (Pan & Zoom)" }) 1408 ] }), 1409 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 1410 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[11px] font-mono text-amber-300 font-semibold bg-amber-500/10 border border-amber-500/20 px-2 py-0.5 rounded-full", children: [ 1411 effectiveScale.toFixed(2), 1412 "x ⢠", 1413 effectiveOffsetX > 0 ? `+${effectiveOffsetX}` : effectiveOffsetX, 1414 "%, ", 1415 effectiveOffsetY > 0 ? `+${effectiveOffsetY}` : effectiveOffsetY, 1416 "%" 1417 ] }), 1418 openSections.camera ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { size: 15, className: "text-zinc-400" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 15, className: "text-zinc-400" }) 1419 ] }) 1420 ] 1421 } 1422 ),
1423 openSections.camera && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 border-t border-zinc-800/80 space-y-4 bg-zinc-950/40", children: [ 1424 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center", children: [ 1425 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-zinc-400", children: "Position & Framing:" }), 1426 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1427 "button", 1428 { 1429 type: "button", 1430 onClick: resetStageAlignment, 1431 className: "text-xs text-zinc-400 hover:text-amber-300 flex items-center gap-1 transition-colors px-2 py-1 rounded bg-zinc-900 hover:bg-zinc-850 border border-zinc-800", 1432 children: [ 1433 /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { size: 12 }), 1434 " Reset Alignment" 1435 ] 1436 } 1437 ) 1438 ] }), 1439 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
1440 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-1 text-xs", children: [ 1441 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-300 font-medium", children: "Stage Zoom (Scale)" }), 1442 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-amber-400 font-bold", children: [ 1443 effectiveScale.toFixed(2), 1444 "x" 1445 ] }) 1446 ] }), 1447 /* @__PURE__ */ jsxRuntimeExports.jsx( 1448 "input", 1449 { 1450 type: "range", 1451 min: "1.00", 1452 max: "2.50", 1453 step: "0.01", 1454 value: effectiveScale, 1455 onChange: (e) => { 1456 const val = parseFloat(e.target.value); 1457 updateActiveConfig((prev) => ({ 1458 ...prev, 1459 image: { ...prev.image || DEFAULT_STAGE_IMAGE, scale: val } 1460 })); 1461 }, 1462 className: "w-full accent-amber-500 cursor-pointer" 1463 } 1464 ),
1465 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between text-[10px] text-zinc-500 mt-0.5", children: [ 1466 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "1.00x (Wide)" }), 1467 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [ 1468 "Default: ", 1469 activeStageCalib.scale.toFixed(2), 1470 "x" 1471 ] }), 1472 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "2.50x (Close)" }) 1473 ] }) 1474 ] }), 1475 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1476 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-1 text-xs", children: [ 1477 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-300 font-medium", children: "Horizontal Shift (X Offset)" }), 1478 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 1479 effectiveOffsetX !== 0 && /* @__PURE__ */ jsxRuntimeExports.jsx( 1480 "button", 1481 { 1482 type: "button", 1483 onClick: () => { 1484 updateActiveConfig((prev) => ({ 1485 ...prev, 1486 image: { ...prev.image || DEFAULT_STAGE_IMAGE, offsetX: 0 } 1487 })); 1488 }, 1489 className: "text-[10px] text-zinc-400 hover:text-amber-300 transition-colors underline", 1490 children: "Center X" 1491 } 1492 ),
1493 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-amber-400 font-bold", children: [ 1494 effectiveOffsetX > 0 ? `+${effectiveOffsetX}` : effectiveOffsetX, 1495 "%" 1496 ] }) 1497 ] }) 1498 ] }), 1499 /* @__PURE__ */ jsxRuntimeExports.jsx( 1500 "input", 1501 { 1502 type: "range", 1503 min: "-35", 1504 max: "35", 1505 step: "1", 1506 value: effectiveOffsetX, 1507 onChange: (e) => { 1508 const val = parseInt(e.target.value); 1509 updateActiveConfig((prev) => ({ 1510 ...prev, 1511 image: { ...prev.image || DEFAULT_STAGE_IMAGE, offsetX: val } 1512 })); 1513 }, 1514 className: "w-full accent-amber-500 cursor-pointer" 1515 } 1516 ), 1517 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between text-[10px] text-zinc-500 mt-0.5", children: [
1518 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "-35% (Left)" }), 1519 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Center: 0%" }), 1520 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "+35% (Right)" }) 1521 ] }) 1522 ] }), 1523 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1524 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-1 text-xs", children: [ 1525 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-zinc-300 font-medium", children: "Vertical Shift (Y Offset)" }), 1526 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-amber-400 font-bold", children: [ 1527 effectiveOffsetY > 0 ? `+${effectiveOffsetY}` : effectiveOffsetY, 1528 "%" 1529 ] }) 1530 ] }), 1531 /* @__PURE__ */ jsxRuntimeExports.jsx( 1532 "input", 1533 { 1534 type: "range", 1535 min: "-25", 1536 max: "35", 1537 step: "1", 1538 value: effectiveOffsetY, 1539 onChange: (e) => { 1540 const val = parseInt(e.target.value); 1541 updateActiveConfig((prev) => ({ 1542 ...prev, 1543 image: { ...prev.image || DEFAULT_STAGE_IMAGE, offsetY: val } 1544 })); 1545 }, 1546 className: "w-full accent-amber-500 cursor-pointer" 1547 } 1548 ),
1549 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between text-[10px] text-zinc-500 mt-0.5", children: [ 1550 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "-25% (Up)" }), 1551 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [ 1552 "Default: ", 1553 activeStageCalib.offsetY > 0 ? `+${activeStageCalib.offsetY}` : activeStageCalib.offsetY, 1554 "%" 1555 ] }), 1556 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "+35% (Down)" }) 1557 ] }) 1558 ] }), 1559 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pt-2 border-t border-zinc-800/80 text-[11px] text-zinc-500 flex items-center gap-1.5", children: [ 1560 /* @__PURE__ */ jsxRuntimeExports.jsx(Move, { size: 12, className: "text-amber-400" }), 1561 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Tip: Drag stage canvas directly to pan ⢠Scroll mouse wheel to zoom" }) 1562 ] }) 1563 ] }) 1564 ] }),
1565 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-900/80 border border-zinc-800 rounded-xl overflow-hidden shadow-lg transition-all", children: [ 1566 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1567 "button", 1568 { 1569 type: "button", 1570 onClick: () => toggleSection("geometry"), 1571 className: "w-full px-4 py-3 flex items-center justify-between bg-zinc-900 hover:bg-zinc-850 transition-colors text-left select-none", 1572 children: [ 1573 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 1574 /* @__PURE__ */ jsxRuntimeExports.jsx(Users, { size: 16, className: "text-indigo-400" }), 1575 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm font-bold text-white", children: "Musicians & Seating Geometry" }) 1576 ] }), 1577 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
1578 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[11px] font-mono text-indigo-300 font-semibold bg-indigo-500/10 border border-indigo-500/20 px-2 py-0.5 rounded-full", children: [ 1579 sampleMusicianCount, 1580 " musicians ⢠", 1581 stageConfig.baseScale.toFixed(2), 1582 "x scale" 1583 ] }), 1584 openSections.geometry ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { size: 15, className: "text-zinc-400" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 15, className: "text-zinc-400" }) 1585 ] }) 1586 ] 1587 } 1588 ), 1589 openSections.geometry && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 border-t border-zinc-800/80 space-y-4 bg-zinc-950/40", children: [ 1590 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [ 1591 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center text-xs", children: [
1592 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-zinc-300 font-medium", children: [ 1593 "Musicians on Stage: ", 1594 /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { className: "text-amber-400 font-mono", children: sampleMusicianCount }) 1595 ] }), 1596 /* @__PURE__ */ jsxRuntimeExports.jsx( 1597 "button", 1598 { 1599 type: "button", 1600 onClick: () => setEnableDensityScaling(!enableDensityScaling), 1601 className: `text-[10px] px-2 py-0.5 rounded border transition-colors ${enableDensityScaling ? "bg-indigo-500/20 text-indigo-300 border-indigo-500/40" : "bg-zinc-800 text-zinc-400 border-zinc-700"}`, 1602 children: enableDensityScaling ? "Crowd Scale: Auto" : "Crowd Scale: Off" 1603 } 1604 ) 1605 ] }), 1606 /* @__PURE__ */ jsxRuntimeExports.jsx( 1607 "input", 1608 { 1609 type: "range", 1610 min: "1", 1611 max: "24", 1612 step: "1", 1613 value: sampleMusicianCount, 1614 onChange: (e) => setSampleMusicianCount(parseInt(e.target.value)), 1615 className: "w-full accent-amber-500 cursor-pointer" 1616 } 1617 ),
1618 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-1 flex-wrap", children: [ 1619 { count: 1, label: "1 Solo" }, 1620 { count: 3, label: "3 Trio" }, 1621 { count: 5, label: "5 Band" }, 1622 { count: 8, label: "8 Chamber" }, 1623 { count: 12, label: "12 Big Band" }, 1624 { count: 18, label: "18 Orchestra" }, 1625 { count: 24, label: "24 Symphony" } 1626 ].map((preset) => /* @__PURE__ */ jsxRuntimeExports.jsx( 1627 "button", 1628 { 1629 type: "button", 1630 onClick: () => setSampleMusicianCount(preset.count), 1631 className: `px-2 py-1 rounded text-[10px] font-semibold border transition-all ${sampleMusicianCount === preset.count ? "bg-amber-500 text-black border-amber-400 shadow-sm" : "bg-zinc-900 hover:bg-zinc-800 text-zinc-400 hover:text-white border-zinc-800"}`, 1632 children: preset.label 1633 }, 1634 preset.count 1635 )) }) 1636 ] }),
1637 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-3 pt-2 border-t border-zinc-800/80", children: [ 1638 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1639 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-1 text-xs", children: [ 1640 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "font-semibold text-zinc-200", children: "Puppet Scale" }), 1641 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono font-bold text-amber-400", children: [ 1642 stageConfig.baseScale.toFixed(2), 1643 "x" 1644 ] }) 1645 ] }), 1646 /* @__PURE__ */ jsxRuntimeExports.jsx( 1647 "input", 1648 { 1649 type: "range", 1650 min: "0.5", 1651 max: "2.0", 1652 step: "0.05", 1653 value: stageConfig.baseScale, 1654 onChange: (e) => setStageConfig({ ...stageConfig, baseScale: parseFloat(e.target.value) }), 1655 className: "w-full accent-amber-500 cursor-pointer" 1656 } 1657 ) 1658 ] }),
1659 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1660 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-1 text-xs", children: [ 1661 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "font-semibold text-zinc-200", children: "Row Multiplier" }), 1662 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono font-bold text-indigo-400", children: [ 1663 stageConfig.rowScaleMultiplier.toFixed(2), 1664 "x" 1665 ] }) 1666 ] }), 1667 /* @__PURE__ */ jsxRuntimeExports.jsx( 1668 "input", 1669 { 1670 type: "range", 1671 min: "0.6", 1672 max: "1.2", 1673 step: "0.02", 1674 value: stageConfig.rowScaleMultiplier, 1675 onChange: (e) => setStageConfig({ ...stageConfig, rowScaleMultiplier: parseFloat(e.target.value) }), 1676 className: "w-full accent-indigo-500 cursor-pointer" 1677 } 1678 ) 1679 ] }),
1680 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1681 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-1 text-xs", children: [ 1682 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "font-semibold text-zinc-200", children: "Global Y Offset" }), 1683 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono font-bold text-emerald-400", children: [ 1684 stageConfig.yOffset, 1685 "px" 1686 ] }) 1687 ] }), 1688 /* @__PURE__ */ jsxRuntimeExports.jsx( 1689 "input", 1690 { 1691 type: "range", 1692 min: "-120", 1693 max: "120", 1694 step: "5", 1695 value: stageConfig.yOffset, 1696 onChange: (e) => setStageConfig({ ...stageConfig, yOffset: parseInt(e.target.value) }), 1697 className: "w-full accent-emerald-500 cursor-pointer" 1698 } 1699 ) 1700 ] }),
1701 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1702 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-1 text-xs", children: [ 1703 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "font-semibold text-zinc-200", children: "Arc Curvature" }), 1704 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono font-bold text-rose-400", children: [ 1705 (stageConfig.arcFactor ?? 1).toFixed(2), 1706 "x" 1707 ] }) 1708 ] }), 1709 /* @__PURE__ */ jsxRuntimeExports.jsx( 1710 "input", 1711 { 1712 type: "range", 1713 min: "0.0", 1714 max: "2.5", 1715 step: "0.1", 1716 value: stageConfig.arcFactor ?? 1, 1717 onChange: (e) => setStageConfig({ ...stageConfig, arcFactor: parseFloat(e.target.value) }), 1718 className: "w-full accent-rose-500 cursor-pointer" 1719 } 1720 ) 1721 ] }) 1722 ] }),
1723 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pt-2 border-t border-zinc-800/80 flex justify-between items-center", children: [ 1724 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1725 "button", 1726 { 1727 type: "button", 1728 onClick: () => setShowArcGuides(!showArcGuides), 1729 className: `px-2.5 py-1 rounded text-xs font-medium border flex items-center gap-1.5 transition-colors ${showArcGuides ? "bg-indigo-500/20 text-indigo-300 border-indigo-500/40" : "bg-zinc-900 text-zinc-500 border-zinc-800 hover:text-white"}`, 1730 children: [ 1731 /* @__PURE__ */ jsxRuntimeExports.jsx(Compass, { size: 12 }), 1732 " ", 1733 showArcGuides ? "Hide Arc Guides" : "Show Arc Guides" 1734 ] 1735 } 1736 ), 1737 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1738 "button", 1739 { 1740 type: "button", 1741 onClick: () => setStageConfig({ 1742 rows: 4, 1743 spacingX: 80, 1744 spacingY: 60, 1745 baseScale: 1, 1746 rowScaleMultiplier: 0.9, 1747 yOffset: 0, 1748 arcFactor: 1 1749 }), 1750 className: "text-xs text-zinc-400 hover:text-white flex items-center gap-1 transition-colors", 1751 children: [
1752 /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { size: 12 }), 1753 " Reset Geometry" 1754 ] 1755 } 1756 ) 1757 ] }) 1758 ] }) 1759 ] }), 1760 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-900/80 border border-zinc-800 rounded-xl overflow-hidden shadow-lg transition-all", children: [ 1761 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1762 "button", 1763 { 1764 type: "button", 1765 onClick: () => toggleSection("atmosphere"), 1766 className: "w-full px-4 py-3 flex items-center justify-between bg-zinc-900 hover:bg-zinc-850 transition-colors text-left select-none", 1767 children: [ 1768 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 1769 /* @__PURE__ */ jsxRuntimeExports.jsx(Lightbulb, { size: 16, className: "text-amber-400" }), 1770 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm font-bold text-white", children: "Stage Lighting & Atmosphere" }) 1771 ] }),
1772 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 1773 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[11px] font-mono text-amber-300 font-semibold bg-amber-500/10 border border-amber-500/20 px-2 py-0.5 rounded-full capitalize", children: [ 1774 previewLighting, 1775 " ⢠", 1776 previewPhysics 1777 ] }), 1778 openSections.atmosphere ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { size: 15, className: "text-zinc-400" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 15, className: "text-zinc-400" }) 1779 ] }) 1780 ] 1781 } 1782 ), 1783 openSections.atmosphere && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 border-t border-zinc-800/80 space-y-4 bg-zinc-950/40", children: [ 1784 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1.5", children: [
1785 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-semibold text-zinc-300 block", children: "Lighting Mode" }), 1786 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-3 gap-1.5", children: ["dynamic", "subtle", "off"].map((mode) => /* @__PURE__ */ jsxRuntimeExports.jsx( 1787 "button", 1788 { 1789 type: "button", 1790 onClick: () => setPreviewLighting(mode), 1791 className: `py-1.5 text-xs font-semibold rounded-lg capitalize border transition-all ${previewLighting === mode ? "bg-amber-500/20 text-amber-300 border-amber-500/40 shadow-sm" : "bg-zinc-900 text-zinc-400 border-zinc-800 hover:text-white"}`, 1792 children: mode 1793 }, 1794 mode 1795 )) }) 1796 ] }), 1797 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-3 pt-2 border-t border-zinc-800/80", children: [ 1798 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
1799 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-semibold text-zinc-300 block mb-1", children: "Character Physics" }), 1800 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-2 gap-1", children: ["snappy", "smooth"].map((p) => /* @__PURE__ */ jsxRuntimeExports.jsx( 1801 "button", 1802 { 1803 type: "button", 1804 onClick: () => setPreviewPhysics(p), 1805 className: `py-1 text-xs rounded border capitalize font-medium ${previewPhysics === p ? "bg-indigo-600 text-white border-indigo-500" : "bg-zinc-900 text-zinc-400 border-zinc-800"}`, 1806 children: p 1807 }, 1808 p 1809 )) }) 1810 ] }), 1811 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1812 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-1", children: [ 1813 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-semibold text-zinc-300", children: "Background Blur" }),
1814 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs font-mono text-zinc-400", children: [ 1815 ((_o = activeConfig.image) == null ? void 0 : _o.blur) ?? 0, 1816 "px" 1817 ] }) 1818 ] }), 1819 /* @__PURE__ */ jsxRuntimeExports.jsx( 1820 "input", 1821 { 1822 type: "range", 1823 min: "0", 1824 max: "20", 1825 step: "1", 1826 value: ((_p = activeConfig.image) == null ? void 0 : _p.blur) ?? 0, 1827 onChange: (e) => updateActiveConfig((prev) => ({ 1828 ...prev, 1829 image: { ...prev.image || DEFAULT_STAGE_IMAGE, blur: parseInt(e.target.value) } 1830 })), 1831 className: "w-full accent-indigo-500 cursor-pointer" 1832 } 1833 ) 1834 ] }) 1835 ] }), 1836 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pt-2 border-t border-zinc-800/80 space-y-2.5", children: [
1837 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [ 1838 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-semibold text-zinc-200", children: "Atmospheric Tint Overlay" }), 1839 /* @__PURE__ */ jsxRuntimeExports.jsx( 1840 "input", 1841 { 1842 type: "checkbox", 1843 checked: ((_q = activeConfig.overlay) == null ? void 0 : _q.enabled) ?? false, 1844 onChange: (e) => updateActiveConfig((prev) => ({ 1845 ...prev, 1846 overlay: { ...prev.overlay || DEFAULT_STAGE_OVERLAY, enabled: e.target.checked } 1847 })), 1848 className: "w-4 h-4 accent-indigo-600 rounded cursor-pointer" 1849 } 1850 ) 1851 ] }), 1852 ((_r = activeConfig.overlay) == null ? void 0 : _r.enabled) && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-3 bg-zinc-950 p-2.5 rounded-lg border border-zinc-800", children: [
1853 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 1854 /* @__PURE__ */ jsxRuntimeExports.jsx( 1855 "input", 1856 { 1857 type: "color", 1858 value: ((_s = activeConfig.overlay) == null ? void 0 : _s.color) || "#000000", 1859 onChange: (e) => updateActiveConfig((prev) => ({ 1860 ...prev, 1861 overlay: { ...prev.overlay || DEFAULT_STAGE_OVERLAY, color: e.target.value } 1862 })), 1863 className: "w-6 h-6 rounded border border-zinc-700 bg-transparent cursor-pointer" 1864 } 1865 ), 1866 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-mono text-zinc-300", children: (_t = activeConfig.overlay) == null ? void 0 : _t.color }) 1867 ] }), 1868 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1869 /* @__P
1869URE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-0.5", children: [ 1870 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] text-zinc-400", children: "Opacity" }), 1871 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[11px] font-mono text-indigo-400", children: [ 1872 Math.round((((_u = activeConfig.overlay) == null ? void 0 : _u.opacity) ?? 0.15) * 100), 1873 "%" 1874 ] }) 1875 ] }), 1876 /* @__PURE__ */ jsxRuntimeExports.jsx( 1877 "input", 1878 { 1879 type: "range", 1880 min: "0", 1881 max: "1", 1882 step: "0.05", 1883 value: ((_v = activeConfig.overlay) == null ? void 0 : _v.opacity) ?? 0.15, 1884 onChange: (e) => updateActiveConfig((prev) => ({ 1885 ...prev, 1886 overlay: { ...prev.overlay || DEFAULT_STAGE_OVERLAY, opacity: parseFloat(e.target.value) } 1887 })), 1888 className: "w-full accent-indigo-500 cursor-pointer h-1.5" 1889 } 1890 ) 1891 ] }) 1892 ] }) 1893 ] }),
1894 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pt-2 border-t border-zinc-800/80 space-y-2.5", children: [ 1895 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [ 1896 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "text-xs font-semibold text-zinc-200 flex items-center gap-1.5", children: [ 1897 /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { size: 13, className: "text-indigo-400" }), 1898 " Particle System" 1899 ] }), 1900 /* @__PURE__ */ jsxRuntimeExports.jsx( 1901 "input", 1902 { 1903 type: "checkbox", 1904 checked: ((_w = activeConfig.particles) == null ? void 0 : _w.enabled) ?? false, 1905 onChange: (e) => updateActiveConfig((prev) => ({ 1906 ...prev, 1907 particles: { ...prev.particles || DEFAULT_STAGE_PARTICLES, enabled: e.target.checked } 1908 })), 1909 className: "w-4 h-4 accent-indigo-600 rounded cursor-pointer" 1910 } 1911 ) 1912 ] }), 1913 ((_x = activeConfig.particles) == null ? void 0 : _x.enabled) && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2 bg-zinc-950 p-2.5 rounded-lg border border-zinc-800", children: [
1914 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1915 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[11px] text-zinc-400 block mb-1", children: "Style" }), 1916 /* @__PURE__ */ jsxRuntimeExports.jsx( 1917 "select", 1918 { 1919 value: ((_y = activeConfig.particles) == null ? void 0 : _y.type) || "stars", 1920 onChange: (e) => updateActiveConfig((prev) => ({ 1921 ...prev, 1922 particles: { ...prev.particles || DEFAULT_STAGE_PARTICLES, type: e.target.value } 1923 })), 1924 className: "w-full bg-zinc-900 border border-zinc-700 text-zinc-100 text-xs px-2 py-1 rounded-lg focus:outline-none", 1925 children: PARTICLE_OPTIONS.map((opt) => /* @__PURE__ */ jsxRuntimeExports.jsxs("option", { value: opt.id, className: "bg-zinc-900 text-white", children: [ 1926 opt.icon, 1927 " ", 1928 opt.label 1929 ] }, opt.id)) 1930 } 1931 ) 1932 ] }),
1933 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-2", children: [ 1934 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1935 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center text-[11px] text-zinc-400 mb-0.5", children: [ 1936 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Count" }), 1937 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-zinc-300", children: ((_z = activeConfig.particles) == null ? void 0 : _z.count) ?? 50 }) 1938 ] }), 1939 /* @__PURE__ */ jsxRuntimeExports.jsx( 1940 "input", 1941 { 1942 type: "range", 1943 min: "10", 1944 max: "150", 1945 step: "5", 1946 value: ((_A = activeConfig.particles) == null ? void 0 : _A.count) ?? 50, 1947 onChange: (e) => updateActiveConfig((prev) => ({ 1948 ...prev, 1949 particles: { ...prev.particles || DEFAULT_STAGE_PARTICLES, count: parseInt(e.target.value) } 1950 })), 1951 className: "w-full accent-indigo-500 cursor-pointer h-1.5" 1952 } 1953 ) 1954 ] }),
1955 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1956 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center text-[11px] text-zinc-400 mb-0.5", children: [ 1957 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Speed" }), 1958 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-zinc-300", children: [ 1959 (((_B = activeConfig.particles) == null ? void 0 : _B.speed) ?? 1).toFixed(1), 1960 "x" 1961 ] }) 1962 ] }), 1963 /* @__PURE__ */ jsxRuntimeExports.jsx( 1964 "input", 1965 { 1966 type: "range", 1967 min: "0.2", 1968 max: "3.0", 1969 step: "0.1", 1970 value: ((_C = activeConfig.particles) == null ? void 0 : _C.speed) ?? 1, 1971 onChange: (e) => updateActiveConfig((prev) => ({ 1972 ...prev, 1973 particles: { ...prev.particles || DEFAULT_STAGE_PARTICLES, speed: parseFloat(e.target.value) } 1974 })), 1975 className: "w-full accent-indigo-500 cursor-pointer h-1.5" 1976 } 1977 ) 1978 ] }) 1979 ] }) 1980 ] }) 1981 ] }) 1982 ] }) 1983 ] }) 1984 ] }) 1985 ] }), 1986 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-1 xl:grid-cols-12 gap-8 items-start pt-4 border-t border-zinc-800/80", children: [
1987 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "xl:col-span-7 space-y-4", children: [ 1988 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3 border-b border-zinc-800 pb-3", children: [ 1989 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 1990 /* @__PURE__ */ jsxRuntimeExports.jsx(Image, { size: 18, className: "text-indigo-400" }), 1991 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-base font-bold text-white", children: "Stage Backgrounds Gallery" }), 1992 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs text-zinc-500 font-mono", children: [ 1993 "(", 1994 filteredPresets.length + 1, 1995 " stages)" 1996 ] }) 1997 ] }), 1998 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 bg-zinc-950 p-1 rounded-xl border border-zinc-800 text-xs", children: [
1999 /* @__PURE__ */ jsxRuntimeExports.jsx( 2000 "button", 2001 { 2002 onClick: () => setGalleryFilter("all"), 2003 className: `px-2.5 py-1 rounded-lg font-medium transition-colors ${galleryFilter === "all" ? "bg-indigo-600 text-white shadow" : "text-zinc-400 hover:text-white"}`, 2004 children: "All" 2005 } 2006 ), 2007 /* @__PURE__ */ jsxRuntimeExports.jsxs( 2008 "button", 2009 { 2010 onClick: () => setGalleryFilter("sp_overrides"), 2011 className: `px-2.5 py-1 rounded-lg font-medium transition-colors ${galleryFilter === "sp_overrides" ? "bg-indigo-600 text-white shadow" : "text-zinc-400 hover:text-white"}`, 2012 children: [ 2013 "SP Overrides (", 2014 spBackgroundPresets.length, 2015 ")" 2016 ] 2017 } 2018 ), 2019 /* @__PURE__ */ jsxRuntimeExports.jsx( 2020 "button", 2021 { 2022 onClick: () => setGalleryFilter("shared"), 2023 className: `px-2.5 py-1 rounded-lg font-medium transition-colors ${galleryFilter === "shared" ? "bg-indigo-600 text-white shadow" : "text-zinc-400 hover:text-white"}`, 2024 children: "Orchestra" 2025 } 2026 ),
2027 /* @__PURE__ */ jsxRuntimeExports.jsxs( 2028 "button", 2029 { 2030 onClick: () => setGalleryFilter("custom"), 2031 className: `px-2.5 py-1 rounded-lg font-medium transition-colors ${galleryFilter === "custom" ? "bg-indigo-600 text-white shadow" : "text-zinc-400 hover:text-white"}`, 2032 children: [ 2033 "Uploads (", 2034 customBackgrounds.length, 2035 ")" 2036 ] 2037 } 2038 ) 2039 ] }) 2040 ] }), 2041 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative", children: [ 2042 /* @__PURE__ */ jsxRuntimeExports.jsx(Search, { size: 14, className: "absolute left-3 top-1/2 -translate-y-1/2 text-zinc-500" }), 2043 /* @__PURE__ */ jsxRuntimeExports.jsx( 2044 "input", 2045 { 2046 type: "text", 2047 value: searchQuery, 2048 onChange: (e) => setSearchQuery(e.target.value), 2049 placeholder: "Filter presets by name or matching tags...", 2050 className: "w-full bg-zinc-950 border border-zinc-800 rounded-xl pl-8 pr-3 py-1.5 text-xs text-white placeholder-zinc-500 focus:outline-none focus:border-indigo-500" 2051 } 2052 ) 2053 ] }),
2054 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 sm:grid-cols-3 gap-3 max-h-[640px] overflow-y-auto pr-1 custom-scrollbar", children: [ 2055 (galleryFilter === "all" || galleryFilter === "shared") && /* @__PURE__ */ jsxRuntimeExports.jsxs( 2056 "div", 2057 { 2058 onClick: () => setSelectedPresetId(null), 2059 className: `group relative rounded-xl border overflow-hidden cursor-pointer transition-all duration-200 shadow-md ${selectedPresetId === null ? "border-purple-500 ring-2 ring-purple-500/50 scale-[1.02]" : "border-zinc-800 hover:border-zinc-700 bg-zinc-900/60"}`, 2060 children: [ 2061 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "aspect-video w-full bg-gradient-to-br from-purple-950 via-zinc-900 to-indigo-950 relative overflow-hidden flex items-center justify-center", children: [ 2062 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 opacity-30 mix-blend-overlay", children: /* @__PURE__ */ jsxRuntimeExports.jsx( 2063 "img", 2064 { 2065 src: ((_F = (_E = (_D = mergedPresets[shufflePreviewIndex % Math.max(1, mergedPresets.length)]) == null ? void 0 : _D.config) == null ? void 0 : _E.image) == null ? void 0 : _F.url) || "/assets/visualizers/orchestra/bkg/anime.jpg", 2066 alt: "Shuffle Preview", 2067 className: "w-full h-full object-cover group-hover:scale-105 transition-transform duration-300" 2068 } 2069 ) }),
2070 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative z-10 text-center p-3", children: [ 2071 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-2xl block mb-1", children: "ð²" }), 2072 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] font-bold text-purple-200 tracking-wide block", children: "Stage Randomizer" }), 2073 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[9px] text-purple-300/80 font-mono", children: [ 2074 mergedPresets.length, 2075 " Stages Pool" 2076 ] }) 2077 ] }), 2078 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "absolute top-1.5 left-1.5 bg-black/80 backdrop-blur-sm text-[10px] font-bold text-purple-400 px-2 py-0.5 rounded-full border border-purple-500/30 flex items-center gap-1", children: "ð² Dynamic Shuffle" }), 2079 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "absolute top-1.5 right-1.5 bg-purple-600 text-white text-[9px] font-extrabold px-1.5 py-0.5 rounded shadow", children: "Untagged Songs" }) 2080 ] }),
2081 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-2.5 bg-zinc-950/90", children: [ 2082 /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "text-xs font-bold text-white truncate", children: "Dynamic Stage Shuffle" }), 2083 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-zinc-400 truncate mt-0.5", children: "Random stage when no genre matches" }) 2084 ] }) 2085 ] 2086 } 2087 ), 2088 filteredPresets.map((preset) => { 2089 var _a2, _b2; 2090 const isSelected = selectedPresetId === preset.id; 2091 const partMeta = PARTICLE_OPTIONS.find((p) => { 2092 var _a3, _b3; 2093 return p.id === ((_b3 = (_a3 = preset.config) == null ? void 0 : _a3.particles) == null ? void 0 : _b3.type); 2094 }) || PARTICLE_OPTIONS[0]; 2095 return /* @__PURE__ */ jsxRuntimeExports.jsxs( 2096 "div", 2097 { 2098 onClick: () => setSelectedPresetId(preset.id), 2099 className: `group relative rounded-xl border overflow-hidden cursor-pointer transition-all duration-200 shadow-md ${isSelected ? "border-indigo-500 ring-2 ring-indigo-500/50 scale-[1.02]" : "border-zinc-800 hover:border-zinc-700 bg-zinc-900/60"}`, 2100 children: [
2101 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "aspect-video w-full bg-zinc-950 relative overflow-hidden", children: [ 2102 ((_b2 = (_a2 = preset.config) == null ? void 0 : _a2.image) == null ? void 0 : _b2.url) ? /* @__PURE__ */ jsxRuntimeExports.jsx( 2103 "img", 2104 { 2105 src: preset.config.image.url, 2106 alt: preset.name, 2107 className: "w-full h-full object-cover group-hover:scale-105 transition-transform duration-300" 2108 } 2109 ) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-full h-full flex items-center justify-center bg-zinc-900 text-zinc-600", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Image, { size: 24 }) }), 2110 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "absolute top-1.5 left-1.5 bg-black/80 backdrop-blur-sm text-[10px] font-medium text-zinc-300 px-1.5 py-0.5 rounded border border-zinc-700", children: [ 2111 partMeta.icon, 2112 " ", 2113 partMeta.label.split(" ")[0] 2114 ] }), 2115 preset.isSpOverride && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "absolute top-1.5 right-1.5 bg-amber-500 text-black text-[9px] font-extrabold px-1.5 py-0.5 rounded shadow", children: "SP Override" }) 2116 ] }),
2117 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-2.5 bg-zinc-950/90", children: [ 2118 /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "text-xs font-bold text-white truncate", children: preset.name }), 2119 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-zinc-400 truncate mt-0.5", children: preset.tags.length > 0 ? preset.tags.join(", ") : "no tags" }) 2120 ] }) 2121 ] 2122 }, 2123 preset.id 2124 ); 2125 }) 2126 ] }), 2127 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-900/60 border border-dashed border-zinc-800 rounded-xl p-4 space-y-3", children: [ 2128 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between flex-wrap gap-2", children: [ 2129 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 2130 /* @__PURE__ */ jsxRuntimeExports.jsxs("h4", { className: "text-xs font-bold text-white flex items-center gap-1.5", children: [
2131 /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { size: 14, className: "text-indigo-400" }), 2132 " Upload Custom Stage Background" 2133 ] }), 2134 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-zinc-400 mt-0.5", children: "Upload full HD 1920x1080 stage backgrounds (WebP, PNG, JPG)." }) 2135 ] }), 2136 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: `px-3 py-1.5 rounded-lg text-xs font-semibold bg-indigo-600 hover:bg-indigo-500 text-white cursor-pointer shadow transition-colors flex items-center gap-1.5 ${isUploading ? "opacity-50 pointer-events-none" : ""}`, children: [ 2137 /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { size: 13 }), 2138 " ", 2139 isUploading ? "Uploading..." : "Choose File", 2140 /* @__PURE__ */ jsxRuntimeExports.jsx( 2141 "input", 2142 { 2143 ref: fileInputRef, 2144 type: "file", 2145 accept: "image/*", 2146 onChange: handleUploadBackground, 2147 className: "hidden" 2148 } 2149 ) 2150 ] }) 2151 ] }), 2152 uploadError && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-rose-400 bg-rose-500/10 border border-rose-500/20 px-3 py-1.5 rounded-lg", children: uploadError }), 2153 customBackgrounds.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pt-2 border-t border-zinc-800", children: [
2154 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] font-bold uppercase tracking-wider text-zinc-500 block mb-2", children: [ 2155 "Your Custom Uploaded Stages (", 2156 customBackgrounds.length, 2157 ")" 2158 ] }), 2159 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-3 sm:grid-cols-4 gap-2", children: customBackgrounds.map((url) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "group relative rounded-lg border border-zinc-800 overflow-hidden bg-black aspect-video", children: [ 2160 /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: url, alt: "Uploaded custom", className: "w-full h-full object-cover" }), 2161 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute inset-0 bg-black/60 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center gap-2", children: [ 2162 /* @__PURE__ */ jsxRuntimeExports.jsx( 2163 "button", 2164 { 2165 onClick: () => updateActiveConfig((prev) => ({ 2166 ...prev, 2167 type: "image", 2168 image: { ...prev.image || DEFAULT_STAGE_IMAGE, url } 2169 })), 2170 className: "p-1.5 rounded bg-indigo-600 text-white hover:bg-indigo-500", 2171 title: "Apply to active background", 2172 children: /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { size: 12 }) 2173 } 2174 ),
2175 /* @__PURE__ */ jsxRuntimeExports.jsx( 2176 "button", 2177 { 2178 onClick: () => handleDeleteCustomBackground(url), 2179 className: "p-1.5 rounded bg-rose-600 text-white hover:bg-rose-500", 2180 title: "Delete this background", 2181 children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 12 }) 2182 } 2183 ) 2184 ] }) 2185 ] }, url)) }) 2186 ] }) 2187 ] }) 2188 ] }), 2189 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "xl:col-span-5 bg-zinc-900/90 border border-zinc-800 rounded-2xl p-6 shadow-2xl space-y-6", children: [ 2190 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between border-b border-zinc-800 pb-3", children: [ 2191 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 2192 /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "text-base font-bold text-white flex items-center gap-2", children: [
2193 /* @__PURE__ */ jsxRuntimeExports.jsx(SlidersVertical, { size: 16, className: "text-indigo-400" }), 2194 " Background Inspector" 2195 ] }), 2196 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs text-zinc-400", children: [ 2197 "Editing: ", 2198 /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { className: "text-indigo-300", children: activePresetMeta.name }) 2199 ] }) 2200 ] }), 2201 selectedPresetId !== null && /* @__PURE__ */ jsxRuntimeExports.jsx( 2202 "button", 2203 { 2204 onClick: () => { 2205 const newName = prompt("Enter new preset name:", activePresetMeta.name); 2206 if (newName) updatePresetMeta(newName); 2207 }, 2208 className: "text-xs text-zinc-400 hover:text-white underline", 2209 children: "Rename" 2210 } 2211 ) 2212 ] }), 2213 selectedPresetId === null ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3 bg-purple-950/40 border border-purple-500/40 rounded-xl p-4 shadow-md", children: [
2214 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-purple-300", children: [ 2215 /* @__PURE__ */ jsxRuntimeExports.jsx(Shuffle, { size: 16 }), 2216 /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "text-xs font-bold text-white uppercase tracking-wider", children: "Automatic Stage Randomization" }) 2217 ] }), 2218 /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-zinc-300 leading-relaxed", children: [ 2219 "Any song without matching genre tags automatically selects a random background from the pool of all ", 2220 /* @__PURE__ */ jsxRuntimeExports.jsxs("strong", { children: [ 2221 mergedPresets.length, 2222 " illustrated & custom backdrops" 2223 ] }), 2224 "." 2225 ] }), 2226 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pt-2 border-t border-purple-500/20 flex items-center justify-between gap-2 flex-wrap", children: [ 2227 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] text-purple-300 font-medium", children: "Workshop Preview:" }),
2228 /* @__PURE__ */ jsxRuntimeExports.jsxs( 2229 "button", 2230 { 2231 type: "button", 2232 onClick: () => setShufflePreviewIndex((prev) => (prev + 1) % Math.max(1, mergedPresets.length)), 2233 className: "px-2.5 py-1 rounded bg-purple-600 hover:bg-purple-500 text-white text-[11px] font-bold flex items-center gap-1.5 transition-colors shadow", 2234 children: [ 2235 /* @__PURE__ */ jsxRuntimeExports.jsx(Shuffle, { size: 11 }), 2236 " Next Random Stage (", 2237 shufflePreviewIndex + 1, 2238 "/", 2239 mergedPresets.length, 2240 ")" 2241 ] 2242 } 2243 ) 2244 ] }) 2245 ] }) : ( 2246 /* Tag Binding (for presets) */ 2247 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1.5 bg-zinc-950 p-3.5 rounded-xl border border-zinc-800", children: [ 2248 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-bold text-zinc-300 uppercase tracking-wider block", children: "Matching Genre Tags" }),
2249 /* @__PURE__ */ jsxRuntimeExports.jsx( 2250 "input", 2251 { 2252 type: "text", 2253 value: activePresetMeta.tags.join(", "), 2254 onChange: (e) => { 2255 const tags = e.target.value.split(",").map((t) => t.trim()).filter(Boolean); 2256 updatePresetMeta(void 0, tags); 2257 }, 2258 placeholder: "e.g. classical, soundtrack, orchestra, piano", 2259 className: "w-full bg-zinc-900 border border-zinc-700 text-white text-xs px-3 py-2 rounded-lg focus:outline-none focus:border-indigo-500" 2260 } 2261 ), 2262 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-zinc-500", children: "Songs containing any of these tags automatically activate this stage." }) 2263 ] }) 2264 ), 2265 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [ 2266 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-bold text-zinc-400 uppercase tracking-wider block", children: "Background Type" }),
2267 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-3 gap-1.5 bg-zinc-950 p-1 rounded-xl border border-zinc-800", children: ["image", "gradient", "color"].map((type) => /* @__PURE__ */ jsxRuntimeExports.jsx( 2268 "button", 2269 { 2270 onClick: () => updateActiveConfig((prev) => ({ ...prev, type })), 2271 className: `py-1.5 text-xs font-bold rounded-lg capitalize transition-all ${activeConfig.type === type ? "bg-indigo-600 text-white shadow" : "text-zinc-400 hover:text-white"}`, 2272 children: type 2273 }, 2274 type 2275 )) }) 2276 ] }), 2277 activeConfig.type === "image" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4 bg-zinc-950 p-4 rounded-xl border border-zinc-800", children: [ 2278 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1.5", children: [ 2279 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-semibold text-zinc-300", children: "Image Asset URL" }),
2280 /* @__PURE__ */ jsxRuntimeExports.jsx( 2281 "input", 2282 { 2283 type: "text", 2284 value: ((_G = activeConfig.image) == null ? void 0 : _G.url) || "", 2285 onChange: (e) => updateActiveConfig((prev) => ({ 2286 ...prev, 2287 image: { ...prev.image || DEFAULT_STAGE_IMAGE, url: e.target.value } 2288 })), 2289 placeholder: "/assets/visualizers/orchestra/bkg/classical.jpg", 2290 className: "w-full bg-zinc-900 border border-zinc-700 text-white text-xs px-3 py-2 rounded-lg font-mono" 2291 } 2292 ) 2293 ] }), 2294 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-3", children: [ 2295 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 2296 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-semibold text-zinc-300 block mb-1", children: "Image Fit" }), 2297 /* @__PURE__ */ jsxRuntimeExports.jsxs( 2298 "select", 2299 { 2300 value: ((_H = activeConfig.image) == null ? void 0 : _H.size) || "cover", 2301 onChange: (e) => updateActiveConfig((prev) => ({ 2302 ...prev, 2303 image: { ...prev.image || DEFAULT_STAGE_IMAGE, size: e.target.value } 2304 })), 2305 className: "w-full bg-zinc-900 border border-zinc-700 text-zinc-200 text-xs px-2.5 py-1.5 rounded-lg focus:outline-none", 2306 children: [
2307 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "cover", className: "bg-zinc-900 text-white", children: "Cover (Full Stage)" }), 2308 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "contain", className: "bg-zinc-900 text-white", children: "Contain (Letterbox)" }) 2309 ] 2310 } 2311 ) 2312 ] }), 2313 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 2314 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-1", children: [ 2315 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-semibold text-zinc-300", children: "Blur" }), 2316 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs font-mono text-zinc-400", children: [ 2317 ((_I = activeConfig.image) == null ? void 0 : _I.blur) ?? 0, 2318 "px" 2319 ] }) 2320 ] }), 2321 /* @__PURE__ */ jsxRuntimeExports.jsx( 2322 "input", 2323 { 2324 type: "range", 2325 min: "0", 2326 max: "20", 2327 step: "1", 2328 value: ((_J = activeConfig.image) == null ? void 0 : _J.blur) ?? 0, 2329 onChange: (e) => updateActiveConfig((prev) => ({ 2330 ...prev, 2331 image: { ...prev.image || DEFAULT_STAGE_IMAGE, blur: parseInt(e.target.value) } 2332 })), 2333 className: "w-full accent-indigo-500 cursor-pointer" 2334 } 2335 ) 2336 ] }) 2337 ] }) 2338 ] }), 2339 activeConfig.type === "color" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3 bg-zinc-950 p-4 rounded-xl border border-zinc-800", children: [
2340 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-semibold text-zinc-300 block", children: "Solid Background Color" }), 2341 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [ 2342 /* @__PURE__ */ jsxRuntimeExports.jsx( 2343 "input", 2344 { 2345 type: "color", 2346 value: activeConfig.color || "#1e1b4b", 2347 onChange: (e) => updateActiveConfig((prev) => ({ ...prev, color: e.target.value })), 2348 className: "w-8 h-8 rounded border border-zinc-700 bg-transparent cursor-pointer" 2349 } 2350 ), 2351 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-mono text-zinc-200", children: activeConfig.color || "#1e1b4b" }) 2352 ] }) 2353 ] }), 2354 activeConfig.type === "gradient" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3 bg-zinc-950 p-4 rounded-xl border border-zinc-800", children: [
2355 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-xs font-semibold text-zinc-300 block", children: "Gradient Background Colors" }), 2356 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-3", children: [ 2357 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 2358 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] text-zinc-400 block mb-1", children: "Color 1 (Top)" }), 2359 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 2360 /* @__PURE__ */ jsxRuntimeExports.jsx( 2361 "input", 2362 { 2363 type: "color", 2364 value: ((_M = (_L = (_K = activeConfig.gradient) == null ? void 0 : _K.stops) == null ? void 0 : _L[0]) == null ? void 0 : _M.color) || "#0f172a", 2365 onChange: (e) => { 2366 var _a2, _b2, _c2; 2367 const c2 = ((_c2 = (_b2 = (_a2 = activeConfig.gradient) == null ? void 0 : _a2.stops) == null ? void 0 : _b2[1]) == null ? void 0 : _c2.color) || "#1e1b4b"; 2368 updateActiveConfig((prev) => { 2369 var _a3, _b3; 2370 return { 2371 ...prev, 2372 gradient: { 2373 type: ((_a3 = prev.gradient) == null ? void 0 : _a3.type) || "linear", 2374 angle: ((_b3 = prev.gradient) == null ? void 0 : _b3.angle) ?? 180, 2375 stops: [ 2376 { color: e.target.value, offset: 0 }, 2377 { color: c2, offset: 100 } 2378 ] 2379 } 2380 }; 2381 }); 2382 }, 2383 className: "w-7 h-7 rounded border border-zinc-700 bg-transparent cursor-pointer" 2384 } 2385 ),
2386 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-mono text-zinc-300", children: ((_P = (_O = (_N = activeConfig.gradient) == null ? void 0 : _N.stops) == null ? void 0 : _O[0]) == null ? void 0 : _P.color) || "#0f172a" }) 2387 ] }) 2388 ] }), 2389 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 2390 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] text-zinc-400 block mb-1", children: "Color 2 (Bottom)" }), 2391 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 2392 /* @__PURE__ */ jsxRuntimeExports.jsx( 2393 "input", 2394 { 2395 type: "color", 2396 value: ((_S = (_R = (_Q = activeConfig.gradient) == null ? void 0 : _Q.stops) == null ? void 0 : _R[1]) == null ? void 0 : _S.color) || "#1e1b4b", 2397 onChange: (e) => { 2398 var _a2, _b2, _c2; 2399 const c1 = ((_c2 = (_b2 = (_a2 = activeConfig.gradient) == null ? void 0 : _a2.stops) == null ? void 0 : _b2[0]) == null ? void 0 : _c2.color) || "#0f172a"; 2400 updateActiveConfig((prev) => { 2401 var _a3, _b3; 2402 return { 2403 ...prev, 2404 gradient: { 2405 type: ((_a3 = prev.gradient) == null ? void 0 : _a3.type) || "linear", 2406 angle: ((_b3 = prev.gradient) == null ? void 0 : _b3.angle) ?? 180, 2407 stops: [ 2408 { color: c1, offset: 0 }, 2409 { color: e.target.value, offset: 100 } 2410 ] 2411 } 2412 }; 2413 }); 2414 }, 2415 className: "w-7 h-7 rounded border border-zinc-700 bg-transparent cursor-pointer" 2416 } 2417 ),
2418 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-mono text-zinc-300", children: ((_V = (_U = (_T = activeConfig.gradient) == null ? void 0 : _T.stops) == null ? void 0 : _U[1]) == null ? void 0 : _V.color) || "#1e1b4b" }) 2419 ] }) 2420 ] }) 2421 ] }) 2422 ] }), 2423 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3 bg-zinc-950 p-4 rounded-xl border border-zinc-800", children: [ 2424 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 2425 /* @__PURE__ */ jsxRuntimeExports.jsx(CircleQuestionMark, { size: 15, className: "text-indigo-400" }), 2426 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 2427 /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "text-xs font-bold text-zinc-200", children: "Simulate Track Genre / Tag Match" }), 2428 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-zinc-400", children: "Test which stage background is automatically loaded for a given genre or tag." }) 2429 ] }) 2430 ] }),
2431 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative", children: [ 2432 /* @__PURE__ */ jsxRuntimeExports.jsx(Search, { size: 14, className: "absolute left-3 top-1/2 -translate-y-1/2 text-zinc-500" }), 2433 /* @__PURE__ */ jsxRuntimeExports.jsx( 2434 "input", 2435 { 2436 type: "text", 2437 value: simulationTagInput, 2438 onChange: (e) => setSimulationTagInput(e.target.value), 2439 placeholder: "Type tag or genre (e.g. anime, 8-bit, rock, piano)...", 2440 className: "w-full bg-zinc-900 border border-zinc-700 rounded-lg pl-8 pr-3 py-1.5 text-xs text-white focus:outline-none focus:border-indigo-500 font-medium" 2441 } 2442 ) 2443 ] }), 2444 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-wrap items-center gap-1.5", children: POPULAR_SIMULATION_GENRES.slice(0, 8).map((g) => /* @__PURE__ */ jsxRuntimeExports.jsx( 2445 "button", 2446 { 2447 type: "button", 2448 onClick: () => setSimulationTagInput(g.tag), 2449 className: `px-2 py-0.5 rounded text-[10px] transition-colors border ${simulationTagInput.toLowerCase() === g.tag.toLowerCase() ? "bg-indigo-600 text-white border-indigo-500 font-semibold" : "bg-zinc-900 text-zinc-400 border-zinc-800 hover:text-white"}`, 2450 children: g.label 2451 }, 2452 g.tag 2453 )) }), 2454 simulationResult && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-900 border border-zinc-800 rounded-lg p-2.5 flex items-center justify-between gap-3", children: [
2455 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 2456 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-zinc-400 block", children: simulationResult.reason }), 2457 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs font-bold text-indigo-300 flex items-center gap-1.5", children: [ 2458 "ð¯ ", 2459 simulationResult.presetName, 2460 simulationResult.isSp && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] bg-amber-500/20 text-amber-300 px-1 py-0.2 rounded font-semibold", children: "SP Override" }) 2461 ] }) 2462 ] }), 2463 /* @__PURE__ */ jsxRuntimeExports.jsx( 2464 "button", 2465 { 2466 type: "button", 2467 onClick: () => setSelectedPresetId(simulationResult.presetId), 2468 className: "px-2.5 py-1 rounded text-[11px] font-semibold bg-indigo-600/30 hover:bg-indigo-600 text-indigo-200 hover:text-white border border-indigo-500/40 transition-colors shrink-0", 2469 children: "Inspect Stage" 2470 } 2471 ) 2472 ] }) 2473 ] }) 2474 ] }) 2475 ] }) 2476 ] }); 2477}; 2478export { 2479 PARTICLE_OPTIONS, 2480 POPULAR_SIMULATION_GENRES, 2481 SouthparkStageDesigner 2482};
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.