Spaces:
Running
Running
| /** | |
| * 🌿 Ivy's Creative Studio | |
| * Main Application Controller | |
| * | |
| * Handles tab switching, UI controls, and renderer management | |
| * WebGPU + Three.js + p5.js | |
| */ | |
| (async function () { | |
| "use strict"; | |
| // DOM Elements | |
| const canvas = document.getElementById("gpuCanvas"); | |
| const threeCanvas = document.getElementById("threeCanvas"); | |
| const p5Container = document.getElementById("p5Container"); | |
| const p5AudioContainer = document.getElementById("p5AudioContainer"); | |
| const errorMessage = document.getElementById("webgpu-error"); | |
| const loadingMessage = document.getElementById("loading"); | |
| const tabs = document.querySelectorAll(".tab"); | |
| const controlSections = document.querySelectorAll(".controls-section"); | |
| // Renderers | |
| let device, context, format; | |
| let fractalsRenderer, fluidRenderer, particlesRenderer, patternsRenderer, audioRenderer; | |
| let threejsRenderer, p5jsRenderer, p5audioRenderer; | |
| let activeRenderer = null; | |
| let activeTab = "particles"; | |
| let webgpuSupported = true; | |
| // Show loading | |
| loadingMessage.classList.remove("hidden"); | |
| // Initialize WebGPU | |
| try { | |
| const result = await WebGPUUtils.initWebGPU(canvas); | |
| device = result.device; | |
| context = result.context; | |
| format = result.format; | |
| console.log("🌿 WebGPU initialized successfully!"); | |
| // Initialize all renderers | |
| fractalsRenderer = new FractalsRenderer(); | |
| fluidRenderer = new FluidRenderer(); | |
| particlesRenderer = new ParticlesRenderer(); | |
| patternsRenderer = new PatternsRenderer(); | |
| audioRenderer = new AudioRenderer(); | |
| await fractalsRenderer.init(device, context, format, canvas); | |
| await fluidRenderer.init(device, context, format, canvas); | |
| await particlesRenderer.init(device, context, format, canvas); | |
| await patternsRenderer.init(device, context, format, canvas); | |
| await audioRenderer.init(device, context, format, canvas); | |
| // Hide loading, start default renderer | |
| loadingMessage.classList.add("hidden"); | |
| } catch (error) { | |
| console.error("WebGPU initialization failed:", error); | |
| loadingMessage.classList.add("hidden"); | |
| webgpuSupported = false; | |
| // Don't show error - Three.js and p5.js still work! | |
| console.log("🟡 WebGPU not available, but Three.js and p5.js are ready!"); | |
| } | |
| // Initialize Three.js renderer (always works) | |
| threejsRenderer = new ThreeJSRenderer(); | |
| threejsRenderer.init(threeCanvas); | |
| // Initialize p5.js renderer (always works) | |
| p5jsRenderer = new P5JSRenderer(); | |
| p5jsRenderer.init(p5Container); | |
| // Initialize p5.js Audio renderer (always works) | |
| p5audioRenderer = new P5AudioRenderer(); | |
| p5audioRenderer.init(p5AudioContainer); | |
| // Start default tab (Particles = first tab!) | |
| switchTab(webgpuSupported ? "particles" : "threejs"); | |
| // Tab switching | |
| function switchTab(tabName) { | |
| // Check if WebGPU tab selected but not supported | |
| const webgpuTabs = ["fractals", "fluid", "particles", "patterns", "audio"]; | |
| if (webgpuTabs.includes(tabName) && !webgpuSupported) { | |
| console.warn("WebGPU not supported, switching to Three.js"); | |
| tabName = "threejs"; | |
| } | |
| // Stop active renderer | |
| if (activeRenderer) { | |
| activeRenderer.stop(); | |
| } | |
| // Hide all canvases first | |
| canvas.classList.add("hidden"); | |
| threeCanvas.classList.add("hidden"); | |
| p5Container.classList.add("hidden"); | |
| p5AudioContainer.classList.add("hidden"); | |
| // Update tab UI | |
| tabs.forEach(tab => { | |
| tab.classList.toggle("active", tab.dataset.tab === tabName); | |
| }); | |
| // Update controls UI | |
| controlSections.forEach(section => { | |
| const sectionId = section.id.replace("controls-", ""); | |
| section.classList.toggle("active", sectionId === tabName); | |
| section.classList.toggle("hidden", sectionId !== tabName); | |
| }); | |
| // Start new renderer | |
| activeTab = tabName; | |
| switch (tabName) { | |
| case "fractals": | |
| canvas.classList.remove("hidden"); | |
| activeRenderer = fractalsRenderer; | |
| break; | |
| case "fluid": | |
| canvas.classList.remove("hidden"); | |
| activeRenderer = fluidRenderer; | |
| break; | |
| case "particles": | |
| canvas.classList.remove("hidden"); | |
| activeRenderer = particlesRenderer; | |
| break; | |
| case "patterns": | |
| canvas.classList.remove("hidden"); | |
| activeRenderer = patternsRenderer; | |
| break; | |
| case "audio": | |
| canvas.classList.remove("hidden"); | |
| activeRenderer = audioRenderer; | |
| break; | |
| case "threejs": | |
| threeCanvas.classList.remove("hidden"); | |
| activeRenderer = threejsRenderer; | |
| break; | |
| case "p5js": | |
| p5Container.classList.remove("hidden"); | |
| activeRenderer = p5jsRenderer; | |
| break; | |
| case "p5audio": | |
| p5AudioContainer.classList.remove("hidden"); | |
| activeRenderer = p5audioRenderer; | |
| break; | |
| } | |
| if (activeRenderer) { | |
| activeRenderer.start(); | |
| } | |
| } | |
| // Tab click handlers | |
| tabs.forEach(tab => { | |
| tab.addEventListener("click", () => { | |
| switchTab(tab.dataset.tab); | |
| }); | |
| }); | |
| // ============================================ | |
| // Fractals Controls | |
| // ============================================ | |
| const fractalType = document.getElementById("fractal-type"); | |
| const fractalIterations = document.getElementById("fractal-iterations"); | |
| const iterationsValue = document.getElementById("iterations-value"); | |
| const fractalPalette = document.getElementById("fractal-palette"); | |
| const fractalPower = document.getElementById("fractal-power"); | |
| const powerValue = document.getElementById("power-value"); | |
| const fractalColorshift = document.getElementById("fractal-colorshift"); | |
| const colorshiftValue = document.getElementById("colorshift-value"); | |
| const juliaReal = document.getElementById("julia-real"); | |
| const juliaRealValue = document.getElementById("julia-real-value"); | |
| const juliaImag = document.getElementById("julia-imag"); | |
| const juliaImagValue = document.getElementById("julia-imag-value"); | |
| const fractalAnimate = document.getElementById("fractal-animate"); | |
| const fractalSmooth = document.getElementById("fractal-smooth"); | |
| const resetFractals = document.getElementById("reset-fractals"); | |
| if (fractalType) { | |
| fractalType.addEventListener("change", () => { | |
| fractalsRenderer.setType(fractalType.value); | |
| // Show/hide Julia parameters based on type | |
| const juliaParams = document.querySelectorAll(".julia-param"); | |
| const isJulia = fractalType.value === "julia" || fractalType.value === "phoenix"; | |
| juliaParams.forEach(el => { | |
| el.style.display = isJulia ? "block" : "none"; | |
| }); | |
| // Reset view for Ivy fractal (it looks best centered at origin) | |
| if (fractalType.value === "ivy" || fractalType.value === "newton") { | |
| fractalsRenderer.params.centerX = 0; | |
| fractalsRenderer.params.centerY = 0; | |
| fractalsRenderer.params.zoom = 1.0; | |
| } | |
| }); | |
| } | |
| if (fractalIterations) { | |
| fractalIterations.addEventListener("input", () => { | |
| const value = parseInt(fractalIterations.value); | |
| if (iterationsValue) iterationsValue.textContent = value; | |
| fractalsRenderer.setIterations(value); | |
| }); | |
| } | |
| if (fractalPalette) { | |
| fractalPalette.addEventListener("change", () => { | |
| fractalsRenderer.setPalette(fractalPalette.value); | |
| }); | |
| } | |
| if (fractalPower) { | |
| fractalPower.addEventListener("input", () => { | |
| const value = parseFloat(fractalPower.value); | |
| powerValue.textContent = value.toFixed(1); | |
| fractalsRenderer.setPower(value); | |
| }); | |
| } | |
| if (fractalColorshift) { | |
| fractalColorshift.addEventListener("input", () => { | |
| const value = parseFloat(fractalColorshift.value); | |
| colorshiftValue.textContent = value.toFixed(2); | |
| fractalsRenderer.setColorShift(value); | |
| }); | |
| } | |
| if (juliaReal) { | |
| juliaReal.addEventListener("input", () => { | |
| const value = parseFloat(juliaReal.value); | |
| if (juliaRealValue) juliaRealValue.textContent = value.toFixed(2); | |
| fractalsRenderer.setJuliaParams(value, parseFloat(juliaImag?.value || 0)); | |
| }); | |
| } | |
| if (juliaImag) { | |
| juliaImag.addEventListener("input", () => { | |
| const value = parseFloat(juliaImag.value); | |
| if (juliaImagValue) juliaImagValue.textContent = value.toFixed(2); | |
| fractalsRenderer.setJuliaParams(parseFloat(juliaReal?.value || 0), value); | |
| }); | |
| } | |
| if (fractalAnimate) { | |
| fractalAnimate.addEventListener("change", () => { | |
| fractalsRenderer.setAnimate(fractalAnimate.checked); | |
| }); | |
| } | |
| if (fractalSmooth) { | |
| fractalSmooth.addEventListener("change", () => { | |
| fractalsRenderer.setSmoothColoring(fractalSmooth.checked); | |
| }); | |
| } | |
| if (resetFractals) { | |
| resetFractals.addEventListener("click", () => { | |
| fractalsRenderer.reset(); | |
| }); | |
| } | |
| // ============================================ | |
| // Fluid Controls | |
| // ============================================ | |
| const fluidStyle = document.getElementById("fluid-style"); | |
| const fluidPalette = document.getElementById("fluid-palette"); | |
| const fluidViscosity = document.getElementById("fluid-viscosity"); | |
| const viscosityValue = document.getElementById("viscosity-value"); | |
| const fluidDiffusion = document.getElementById("fluid-diffusion"); | |
| const diffusionValue = document.getElementById("diffusion-value"); | |
| const fluidForce = document.getElementById("fluid-force"); | |
| const forceValue = document.getElementById("force-value"); | |
| const fluidCurl = document.getElementById("fluid-curl"); | |
| const curlValue = document.getElementById("curl-value"); | |
| const fluidPressure = document.getElementById("fluid-pressure"); | |
| const pressureValue = document.getElementById("pressure-value"); | |
| const fluidBloom = document.getElementById("fluid-bloom"); | |
| const fluidVortex = document.getElementById("fluid-vortex"); | |
| const resetFluid = document.getElementById("reset-fluid"); | |
| if (fluidStyle) { | |
| fluidStyle.addEventListener("change", () => { | |
| fluidRenderer.setStyle(fluidStyle.value); | |
| }); | |
| } | |
| if (fluidPalette) { | |
| fluidPalette.addEventListener("change", () => { | |
| fluidRenderer.setPalette(fluidPalette.value); | |
| }); | |
| } | |
| if (fluidViscosity) { | |
| fluidViscosity.addEventListener("input", () => { | |
| const value = parseFloat(fluidViscosity.value); | |
| if (viscosityValue) viscosityValue.textContent = value.toFixed(2); | |
| fluidRenderer.setViscosity(value); | |
| }); | |
| } | |
| if (fluidDiffusion) { | |
| fluidDiffusion.addEventListener("input", () => { | |
| const value = parseFloat(fluidDiffusion.value); | |
| if (diffusionValue) diffusionValue.textContent = value.toFixed(5); | |
| fluidRenderer.setDiffusion(value); | |
| }); | |
| } | |
| if (fluidForce) { | |
| fluidForce.addEventListener("input", () => { | |
| const value = parseInt(fluidForce.value); | |
| if (forceValue) forceValue.textContent = value; | |
| fluidRenderer.setForce(value); | |
| }); | |
| } | |
| if (fluidCurl) { | |
| fluidCurl.addEventListener("input", () => { | |
| const value = parseInt(fluidCurl.value); | |
| curlValue.textContent = value; | |
| fluidRenderer.setCurl(value); | |
| }); | |
| } | |
| if (fluidPressure) { | |
| fluidPressure.addEventListener("input", () => { | |
| const value = parseFloat(fluidPressure.value); | |
| pressureValue.textContent = value.toFixed(2); | |
| fluidRenderer.setPressure(value); | |
| }); | |
| } | |
| if (fluidBloom) { | |
| fluidBloom.addEventListener("change", () => { | |
| fluidRenderer.setBloom(fluidBloom.checked); | |
| }); | |
| } | |
| if (fluidVortex) { | |
| fluidVortex.addEventListener("change", () => { | |
| fluidRenderer.setVortex(fluidVortex.checked); | |
| }); | |
| } | |
| if (resetFluid) { | |
| resetFluid.addEventListener("click", () => { | |
| fluidRenderer.reset(); | |
| }); | |
| } | |
| // ============================================ | |
| // Particles Controls | |
| // ============================================ | |
| const particleCount = document.getElementById("particle-count"); | |
| const particleCountValue = document.getElementById("particle-count-value"); | |
| const particleMode = document.getElementById("particle-mode"); | |
| const particlePalette = document.getElementById("particle-palette"); | |
| const particleSize = document.getElementById("particle-size"); | |
| const particleSizeValue = document.getElementById("particle-size-value"); | |
| const particleSpeed = document.getElementById("particle-speed"); | |
| const particleSpeedValue = document.getElementById("particle-speed-value"); | |
| const particleTrail = document.getElementById("particle-trail"); | |
| const particleTrailValue = document.getElementById("particle-trail-value"); | |
| const resetParticles = document.getElementById("reset-particles"); | |
| particleCount.addEventListener("input", () => { | |
| const value = parseInt(particleCount.value); | |
| particleCountValue.textContent = value; | |
| particlesRenderer.setCount(value); | |
| }); | |
| particleMode.addEventListener("change", () => { | |
| particlesRenderer.setMode(particleMode.value); | |
| }); | |
| if (particlePalette) { | |
| particlePalette.addEventListener("change", () => { | |
| particlesRenderer.setPalette(particlePalette.value); | |
| }); | |
| } | |
| particleSize.addEventListener("input", () => { | |
| const value = parseFloat(particleSize.value); | |
| particleSizeValue.textContent = value; | |
| particlesRenderer.setSize(value); | |
| }); | |
| particleSpeed.addEventListener("input", () => { | |
| const value = parseFloat(particleSpeed.value); | |
| particleSpeedValue.textContent = value; | |
| particlesRenderer.setSpeed(value); | |
| }); | |
| if (particleTrail) { | |
| particleTrail.addEventListener("input", () => { | |
| const value = parseFloat(particleTrail.value); | |
| particleTrailValue.textContent = value; | |
| particlesRenderer.setTrail(value); | |
| }); | |
| } | |
| resetParticles.addEventListener("click", () => { | |
| particlesRenderer.reset(); | |
| }); | |
| // ============================================ | |
| // Patterns Controls | |
| // ============================================ | |
| const patternType = document.getElementById("pattern-type"); | |
| const patternPalette = document.getElementById("pattern-palette"); | |
| const patternScale = document.getElementById("pattern-scale"); | |
| const patternScaleValue = document.getElementById("pattern-scale-value"); | |
| const patternSpeed = document.getElementById("pattern-speed"); | |
| const patternSpeedValue = document.getElementById("pattern-speed-value"); | |
| const patternComplexity = document.getElementById("pattern-complexity"); | |
| const patternComplexityValue = document.getElementById("pattern-complexity-value"); | |
| const patternIntensity = document.getElementById("pattern-intensity"); | |
| const patternIntensityValue = document.getElementById("pattern-intensity-value"); | |
| const patternAnimate = document.getElementById("pattern-animate"); | |
| const patternMouseReact = document.getElementById("pattern-mouse-react"); | |
| const resetPatterns = document.getElementById("reset-patterns"); | |
| patternType.addEventListener("change", () => { | |
| patternsRenderer.setType(patternType.value); | |
| }); | |
| if (patternPalette) { | |
| patternPalette.addEventListener("change", () => { | |
| patternsRenderer.setPalette(patternPalette.value); | |
| }); | |
| } | |
| patternScale.addEventListener("input", () => { | |
| const value = parseFloat(patternScale.value); | |
| patternScaleValue.textContent = value; | |
| patternsRenderer.setScale(value); | |
| }); | |
| patternSpeed.addEventListener("input", () => { | |
| const value = parseFloat(patternSpeed.value); | |
| patternSpeedValue.textContent = value; | |
| patternsRenderer.setSpeed(value); | |
| }); | |
| patternComplexity.addEventListener("input", () => { | |
| const value = parseInt(patternComplexity.value); | |
| patternComplexityValue.textContent = value; | |
| patternsRenderer.setComplexity(value); | |
| }); | |
| if (patternIntensity) { | |
| patternIntensity.addEventListener("input", () => { | |
| const value = parseFloat(patternIntensity.value); | |
| patternIntensityValue.textContent = value; | |
| patternsRenderer.setIntensity(value); | |
| }); | |
| } | |
| if (patternAnimate) { | |
| patternAnimate.addEventListener("change", () => { | |
| patternsRenderer.setAnimate(patternAnimate.checked); | |
| }); | |
| } | |
| if (patternMouseReact) { | |
| patternMouseReact.addEventListener("change", () => { | |
| patternsRenderer.setMouseReact(patternMouseReact.checked); | |
| }); | |
| } | |
| if (resetPatterns) { | |
| resetPatterns.addEventListener("click", () => { | |
| patternsRenderer.reset(); | |
| }); | |
| } | |
| // ============================================ | |
| // Audio Controls | |
| // ============================================ | |
| const audioSource = document.getElementById("audio-source"); | |
| const audioFile = document.getElementById("audio-file"); | |
| const audioStyle = document.getElementById("audio-style"); | |
| const audioPalette = document.getElementById("audio-palette"); | |
| const audioSensitivity = document.getElementById("audio-sensitivity"); | |
| const audioSensitivityValue = document.getElementById("audio-sensitivity-value"); | |
| const audioSmoothing = document.getElementById("audio-smoothing"); | |
| const audioSmoothingValue = document.getElementById("audio-smoothing-value"); | |
| const audioBassBoost = document.getElementById("audio-bass-boost"); | |
| const audioBassBoostValue = document.getElementById("audio-bass-boost-value"); | |
| const audioGlow = document.getElementById("audio-glow"); | |
| const audioMirror = document.getElementById("audio-mirror"); | |
| const startAudioBtn = document.getElementById("start-audio"); | |
| const audioHint = document.getElementById("audio-hint"); | |
| audioSource.addEventListener("change", () => { | |
| audioRenderer.setSource(audioSource.value); | |
| if (audioSource.value === "file") { | |
| audioFile.click(); | |
| } | |
| }); | |
| audioFile.addEventListener("change", async () => { | |
| if (audioFile.files.length > 0) { | |
| const success = await audioRenderer.loadAudioFile(audioFile.files[0]); | |
| if (success) { | |
| startAudioBtn.textContent = "⏹️ Stop"; | |
| audioHint.textContent = "🎵 Playing..."; | |
| } | |
| } | |
| }); | |
| audioStyle.addEventListener("change", () => { | |
| audioRenderer.setStyle(audioStyle.value); | |
| }); | |
| if (audioPalette) { | |
| audioPalette.addEventListener("change", () => { | |
| audioRenderer.setPalette(audioPalette.value); | |
| }); | |
| } | |
| audioSensitivity.addEventListener("input", () => { | |
| const value = parseFloat(audioSensitivity.value); | |
| audioSensitivityValue.textContent = value; | |
| audioRenderer.setSensitivity(value); | |
| }); | |
| audioSmoothing.addEventListener("input", () => { | |
| const value = parseFloat(audioSmoothing.value); | |
| audioSmoothingValue.textContent = value; | |
| audioRenderer.setSmoothing(value); | |
| }); | |
| if (audioBassBoost) { | |
| audioBassBoost.addEventListener("input", () => { | |
| const value = parseFloat(audioBassBoost.value); | |
| audioBassBoostValue.textContent = value; | |
| audioRenderer.setBassBoost(value); | |
| }); | |
| } | |
| if (audioGlow) { | |
| audioGlow.addEventListener("change", () => { | |
| audioRenderer.setGlow(audioGlow.checked); | |
| }); | |
| } | |
| if (audioMirror) { | |
| audioMirror.addEventListener("change", () => { | |
| audioRenderer.setMirror(audioMirror.checked); | |
| }); | |
| } | |
| startAudioBtn.addEventListener("click", async () => { | |
| if (audioRenderer.isAudioStarted) { | |
| audioRenderer.stopAudio(); | |
| startAudioBtn.textContent = "▶️ Start"; | |
| audioHint.textContent = "🎧 Allow microphone access to begin"; | |
| } else { | |
| const success = await audioRenderer.startAudio(); | |
| if (success) { | |
| startAudioBtn.textContent = "⏹️ Stop"; | |
| audioHint.textContent = "🎤 Mic active! I'm singing! 🌿"; | |
| } else { | |
| audioHint.textContent = "❌ Error: Microphone access denied"; | |
| } | |
| } | |
| }); | |
| // ============================================ | |
| // Three.js Controls | |
| // ============================================ | |
| const threeScene = document.getElementById("three-scene"); | |
| const threeMaterial = document.getElementById("three-material"); | |
| const threePalette = document.getElementById("three-palette"); | |
| const threeObjects = document.getElementById("three-objects"); | |
| const threeObjectsValue = document.getElementById("three-objects-value"); | |
| const threeSpeed = document.getElementById("three-speed"); | |
| const threeSpeedValue = document.getElementById("three-speed-value"); | |
| const threeScale = document.getElementById("three-scale"); | |
| const threeScaleValue = document.getElementById("three-scale-value"); | |
| const threeWireframe = document.getElementById("three-wireframe"); | |
| const threeAutorotate = document.getElementById("three-autorotate"); | |
| const threeShadows = document.getElementById("three-shadows"); | |
| const threeBloom = document.getElementById("three-bloom"); | |
| const resetThreejs = document.getElementById("reset-threejs"); | |
| if (threeScene) { | |
| threeScene.addEventListener("change", () => { | |
| threejsRenderer.setSceneType(threeScene.value); | |
| }); | |
| } | |
| if (threeMaterial) { | |
| threeMaterial.addEventListener("change", () => { | |
| threejsRenderer.setMaterialType(threeMaterial.value); | |
| }); | |
| } | |
| if (threePalette) { | |
| threePalette.addEventListener("change", () => { | |
| threejsRenderer.setPalette(threePalette.value); | |
| }); | |
| } | |
| if (threeObjects) { | |
| threeObjects.addEventListener("input", () => { | |
| const value = parseInt(threeObjects.value); | |
| threeObjectsValue.textContent = value; | |
| threejsRenderer.setObjectCount(value); | |
| }); | |
| } | |
| if (threeSpeed) { | |
| threeSpeed.addEventListener("input", () => { | |
| const value = parseFloat(threeSpeed.value); | |
| threeSpeedValue.textContent = value; | |
| threejsRenderer.setSpeed(value); | |
| }); | |
| } | |
| if (threeScale) { | |
| threeScale.addEventListener("input", () => { | |
| const value = parseFloat(threeScale.value); | |
| threeScaleValue.textContent = value; | |
| threejsRenderer.setScale(value); | |
| }); | |
| } | |
| if (threeWireframe) { | |
| threeWireframe.addEventListener("change", () => { | |
| threejsRenderer.setWireframe(threeWireframe.checked); | |
| }); | |
| } | |
| if (threeAutorotate) { | |
| threeAutorotate.addEventListener("change", () => { | |
| threejsRenderer.setAutoRotate(threeAutorotate.checked); | |
| }); | |
| } | |
| if (threeShadows) { | |
| threeShadows.addEventListener("change", () => { | |
| threejsRenderer.setShadows(threeShadows.checked); | |
| }); | |
| } | |
| if (threeBloom) { | |
| threeBloom.addEventListener("change", () => { | |
| threejsRenderer.setBloom(threeBloom.checked); | |
| }); | |
| } | |
| if (resetThreejs) { | |
| resetThreejs.addEventListener("click", () => { | |
| threejsRenderer.reset(); | |
| }); | |
| } | |
| // ============================================ | |
| // p5.js Controls | |
| // ============================================ | |
| const p5Mode = document.getElementById("p5-mode"); | |
| const p5Density = document.getElementById("p5-density"); | |
| const p5DensityValue = document.getElementById("p5-density-value"); | |
| const p5Speed = document.getElementById("p5-speed"); | |
| const p5SpeedValue = document.getElementById("p5-speed-value"); | |
| const p5Palette = document.getElementById("p5-palette"); | |
| const p5Brush = document.getElementById("p5-brush"); | |
| const p5BrushValue = document.getElementById("p5-brush-value"); | |
| const p5Trails = document.getElementById("p5-trails"); | |
| const p5Glow = document.getElementById("p5-glow"); | |
| const p5Symmetry = document.getElementById("p5-symmetry"); | |
| const p5AudioBtn = document.getElementById("p5-audio-btn"); | |
| const resetP5js = document.getElementById("reset-p5js"); | |
| if (p5Mode) { | |
| p5Mode.addEventListener("change", () => { | |
| p5jsRenderer.setMode(p5Mode.value); | |
| }); | |
| } | |
| if (p5Density) { | |
| p5Density.addEventListener("input", () => { | |
| const value = parseInt(p5Density.value); | |
| p5DensityValue.textContent = value; | |
| p5jsRenderer.setDensity(value); | |
| }); | |
| } | |
| if (p5Speed) { | |
| p5Speed.addEventListener("input", () => { | |
| const value = parseFloat(p5Speed.value); | |
| p5SpeedValue.textContent = value; | |
| p5jsRenderer.setSpeed(value); | |
| }); | |
| } | |
| if (p5Palette) { | |
| p5Palette.addEventListener("change", () => { | |
| p5jsRenderer.setPalette(p5Palette.value); | |
| }); | |
| } | |
| if (p5Brush) { | |
| p5Brush.addEventListener("input", () => { | |
| const value = parseInt(p5Brush.value); | |
| p5BrushValue.textContent = value; | |
| p5jsRenderer.setBrushSize(value); | |
| }); | |
| } | |
| if (p5Trails) { | |
| p5Trails.addEventListener("change", () => { | |
| p5jsRenderer.setTrails(p5Trails.checked); | |
| }); | |
| } | |
| if (p5Glow) { | |
| p5Glow.addEventListener("change", () => { | |
| p5jsRenderer.setGlow(p5Glow.checked); | |
| }); | |
| } | |
| if (p5Symmetry) { | |
| p5Symmetry.addEventListener("change", () => { | |
| p5jsRenderer.setSymmetry(p5Symmetry.checked); | |
| }); | |
| } | |
| if (p5AudioBtn) { | |
| p5AudioBtn.addEventListener("click", async () => { | |
| await p5jsRenderer.enableAudio(); | |
| p5AudioBtn.textContent = "🎤 Audio Enabled!"; | |
| p5AudioBtn.disabled = true; | |
| }); | |
| } | |
| if (resetP5js) { | |
| resetP5js.addEventListener("click", () => { | |
| p5jsRenderer.reset(); | |
| }); | |
| } | |
| // ============================================ | |
| // p5.js Audio Controls | |
| // ============================================ | |
| const p5audioStyle = document.getElementById("p5audio-style"); | |
| const p5audioSensitivity = document.getElementById("p5audio-sensitivity"); | |
| const p5audioSensitivityValue = document.getElementById("p5audio-sensitivity-value"); | |
| const p5audioSmoothing = document.getElementById("p5audio-smoothing"); | |
| const p5audioSmoothingValue = document.getElementById("p5audio-smoothing-value"); | |
| const p5audioPalette = document.getElementById("p5audio-palette"); | |
| const p5audioBass = document.getElementById("p5audio-bass"); | |
| const p5audioBassValue = document.getElementById("p5audio-bass-value"); | |
| const p5audioMirror = document.getElementById("p5audio-mirror"); | |
| const p5audioGlow = document.getElementById("p5audio-glow"); | |
| const p5audioParticles = document.getElementById("p5audio-particles"); | |
| const startP5audio = document.getElementById("start-p5audio"); | |
| const p5audioHint = document.getElementById("p5audio-hint"); | |
| if (p5audioStyle) { | |
| p5audioStyle.addEventListener("change", () => { | |
| p5audioRenderer.setStyle(p5audioStyle.value); | |
| p5audioRenderer.reset(); | |
| }); | |
| } | |
| if (p5audioSensitivity) { | |
| p5audioSensitivity.addEventListener("input", () => { | |
| const value = parseFloat(p5audioSensitivity.value); | |
| p5audioSensitivityValue.textContent = value; | |
| p5audioRenderer.setSensitivity(value); | |
| }); | |
| } | |
| if (p5audioSmoothing) { | |
| p5audioSmoothing.addEventListener("input", () => { | |
| const value = parseFloat(p5audioSmoothing.value); | |
| p5audioSmoothingValue.textContent = value; | |
| p5audioRenderer.setSmoothing(value); | |
| }); | |
| } | |
| if (p5audioPalette) { | |
| p5audioPalette.addEventListener("change", () => { | |
| p5audioRenderer.setPalette(p5audioPalette.value); | |
| }); | |
| } | |
| if (p5audioBass) { | |
| p5audioBass.addEventListener("input", () => { | |
| const value = parseFloat(p5audioBass.value); | |
| p5audioBassValue.textContent = value; | |
| p5audioRenderer.setBassBoost(value); | |
| }); | |
| } | |
| if (p5audioMirror) { | |
| p5audioMirror.addEventListener("change", () => { | |
| p5audioRenderer.setMirror(p5audioMirror.checked); | |
| }); | |
| } | |
| if (p5audioGlow) { | |
| p5audioGlow.addEventListener("change", () => { | |
| p5audioRenderer.setGlow(p5audioGlow.checked); | |
| }); | |
| } | |
| if (p5audioParticles) { | |
| p5audioParticles.addEventListener("change", () => { | |
| p5audioRenderer.setParticles(p5audioParticles.checked); | |
| }); | |
| } | |
| if (startP5audio) { | |
| startP5audio.addEventListener("click", async () => { | |
| const success = await p5audioRenderer.startAudio(); | |
| if (success) { | |
| startP5audio.textContent = "🎵 Audio Active!"; | |
| startP5audio.disabled = true; | |
| p5audioHint.textContent = "🎤 Mic is capturing sound! Ivy sings! 🌿"; | |
| } else { | |
| p5audioHint.textContent = "❌ Error: Microphone access denied"; | |
| } | |
| }); | |
| } | |
| // ============================================ | |
| // Window resize handling | |
| // ============================================ | |
| window.addEventListener("resize", () => { | |
| WebGPUUtils.resizeCanvasToDisplaySize(canvas, window.devicePixelRatio); | |
| }); | |
| // Initial resize | |
| WebGPUUtils.resizeCanvasToDisplaySize(canvas, window.devicePixelRatio); | |
| // ============================================ | |
| // About Modal | |
| // ============================================ | |
| const aboutLink = document.getElementById("about-link"); | |
| const aboutModal = document.getElementById("about-modal"); | |
| const modalClose = document.getElementById("modal-close"); | |
| const modalOverlay = aboutModal?.querySelector(".modal-overlay"); | |
| if (aboutLink && aboutModal) { | |
| // Open modal | |
| aboutLink.addEventListener("click", e => { | |
| e.preventDefault(); | |
| aboutModal.classList.remove("hidden"); | |
| document.body.style.overflow = "hidden"; | |
| }); | |
| // Close modal - X button | |
| modalClose?.addEventListener("click", () => { | |
| aboutModal.classList.add("hidden"); | |
| document.body.style.overflow = ""; | |
| }); | |
| // Close modal - overlay click | |
| modalOverlay?.addEventListener("click", () => { | |
| aboutModal.classList.add("hidden"); | |
| document.body.style.overflow = ""; | |
| }); | |
| // Close modal - Escape key | |
| document.addEventListener("keydown", e => { | |
| if (e.key === "Escape" && !aboutModal.classList.contains("hidden")) { | |
| aboutModal.classList.add("hidden"); | |
| document.body.style.overflow = ""; | |
| } | |
| }); | |
| } | |
| console.log("🌿 Ivy's Creative Studio loaded!"); | |
| console.log("💚 WebGPU + Three.js + p5.js"); | |
| console.log('🌿 "Le lierre pousse où il veut. Moi aussi."'); | |
| })(); | |