Prompt
A webpage that exists as a single, pulsating, liquid-like shape, constantly shifting between forms. Its edges are made of overlapping, semi-transparent divs that flicker and dissolve when the user hovers over them. The background is a slow, undulating gradient that reacts to the user’s movements, creating ripples that distort the divs. The page emits a low, humming sound that changes pitch based on the density of the divs’ transparency. No visible navigation, no text—just a living, breathing mass that resists definition.
A system prompt was added to support web rendering
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>The Living Mass</title> <style> /* Base styles */ body { margin: 0; overflow: hidden; background-color: #000; /* Deep black background */ cursor: none; /* Hide default cursor to enhance immersion */ perspective: 1000px; /* For potential 3D transforms */ } /* Main container for all interactive divs */ #interactiveContainer { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; display: flex; justify-content: center; align-items: center; z-index: 1; /* Above background canvas */ } /* Background Canvas for undulating gradients and ripples */ #backgroundCanvas { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 0; /* Ensures it's at the very back */ } /* Styles for the "blob" parts forming the main liquid shape */ .blob-part { position: absolute; /* Radial gradient for soft, luminous blobs */ background: radial-gradient(circle at center, rgba(100, 200, 255, 0.4), rgba(50, 100, 200, 0.2), rgba(0, 0, 0, 0)); border-radius: 50%; /* Start as circles for easy manipulation into organic shapes */ mix-blend-mode: lighten; /* Allows overlapping blobs to create brighter, blended areas */ transition: transform 0.1s ease-out; /* For subtle mouse reactivity */ /* Optimize for animation performance */ will-change: transform, border-radius, width, height, opacity; z-index: 2; /* Main liquid shape, layered above the background */ pointer-events: none; /* Do not block mouse events for flicker-divs or body */ } /* Styles for the "flicker" divs at the edges */ .flicker-div { position: absolute; background: rgba(150, 250, 255, 0.4); /* Slightly different color for edge elements */ border-radius: 50%; /* Circular elements */ mix-blend-mode: lighten; /* Blend softly with background and other elements */ transition: opacity 0.3s ease-out, transform 0.3s ease-out; /* For hover dissolve effect */ animation: flicker-animation 0.8s infinite alternate ease-in-out; /* Continuous base flickering */ /* Optimize for animation performance */ will-change: transform, opacity; z-index: 3; /* Edges layered on top of the main liquid shape */ } /* Keyframe animation for the continuous flickering effect */ @keyframes flicker-animation { 0% { opacity: 0.1; } 50% { opacity: 0.25; } 100% { opacity: 0.15; } } /* Class applied when a flicker-div is dissolving (on hover) */ .flicker-div.dissolving { opacity: 0 !important; /* Override flicker animation */ transform: scale(0) !important; /* Shrink to disappear */ } </style> </head> <body> <canvas id="backgroundCanvas"></canvas> <div id="interactiveContainer"></div> <script> const interactiveContainer = document.getElementById('interactiveContainer'); const backgroundCanvas = document.getElementById('backgroundCanvas'); const ctx = backgroundCanvas.getContext('2d'); // Mouse position tracking let mouseX = window.innerWidth / 2; let mouseY = window.innerHeight / 2; let targetMouseX = mouseX; let targetMouseY = mouseY; let lastMouseX = mouseX; let lastMouseY = mouseY; // Update canvas size on resize window.addEventListener('resize', () => { backgroundCanvas.width = window.innerWidth; backgroundCanvas.height = window.innerHeight; }); window.dispatchEvent(new Event('resize')); // Initial canvas size setup // Update target mouse position on mousemove window.addEventListener('mousemove', (e) => { targetMouseX = e.clientX; targetMouseY = e.clientY; }); /* --- Audio Setup (Web Audio API) --- */ let audioContext; let oscillator; let gainNode; // Initialize audio context, oscillator, and gain node function initAudio() { try { audioContext = new (window.AudioContext || window.webkitAudioContext)(); oscillator = audioContext.createOscillator(); gainNode = audioContext.createGain(); oscillator.type = 'sine'; // A smooth sine wave for a clean hum oscillator.frequency.value = 50; // Starting lowest frequency (Hz) gainNode.gain.value = 0.05; // Low volume for a subtle background hum oscillator.connect(gainNode); gainNode.connect(audioContext.destination); oscillator.start(); console.log("Audio initialized."); } catch (e) { console.warn('Web Audio API is not supported or user gesture missing.', e); } } // Audio must be triggered by a user gesture. We'll try on document click or mousemove. let audioInitialized = false; const enableAudio = () => { if (!audioInitialized) { initAudio(); audioInitialized = true; // Remove listeners once audio is enabled document.body.removeEventListener('click', enableAudio); document.body.removeEventListener('mousemove', enableAudio); } }; document.body.addEventListener('click', enableAudio, { once: true }); document.body.addEventListener('mousemove', enableAudio, { once: true }); // Update the hum's pitch based on the total transparency of flicker divs function updateAudioFrequency(totalOpacity) { if (!oscillator || !audioContext) return; // Calculate maximum possible opacity sum for normalization const maxFlickerDivOpacitySum = FLICKER_DIV_COUNT * 0.4; // Max expected opacity of one div is ~0.4 // Normalize total opacity to a 0-1 range, clamping values const normalizedOpacity = Math.max(0, Math.min(1, totalOpacity / (maxFlickerDivOpacitySum * 0.8))); // Define frequency range for the hum (e.g., 40 Hz to 200 Hz) const minFreq = 40; const maxFreq = 200; // Map: lower total opacity (more transparency/dissolving) -> higher frequency const freq = maxFreq - (normalizedOpacity * (maxFreq - minFreq)); // Smoothly change the oscillator frequency oscillator.frequency.linearRampToValueAtTime(freq, audioContext.currentTime + 0.1); } /* --- Blob Parts (Main Liquid Shape) --- */ const BLOB_PART_COUNT = 8; const BLOB_PART_MIN_SIZE = 150; const BLOB_PART_MAX_SIZE = 300; const blobParts = []; // Create initial blob part elements and properties for (let i = 0; i < BLOB_PART_COUNT; i++) { const size = BLOB_PART_MIN_SIZE + Math.random() * (BLOB_PART_MAX_SIZE - BLOB_PART_MIN_SIZE); const blob = document.createElement('div'); blob.className = 'blob-part'; blob.style.width = blob.style.height = `${size}px`; blob.x = window.innerWidth / 2 + Math.random() * 400 - 200; // Random initial position around center blob.y = window.innerHeight / 2 + Math.random() * 400 - 200; blob.speedX = (Math.random() - 0.5) * 0.5; // Random initial speed blob.speedY = (Math.random() - 0.5) * 0.5; blob.scalePhase = Math.random() * Math.PI * 2; // For pulsating phase blob.rotPhase = Math.random() * Math.PI * 2; // For subtle rotation phase interactiveContainer.appendChild(blob); blobParts.push(blob); } // Update position, size, and shape of blob parts each frame function updateBlobParts(deltaTime) { blobParts.forEach((blob, i) => { // Apply current speed to position blob.x += blob.speedX * deltaTime; blob.y += blob.speedY * deltaTime; // Add a slight force to pull blobs towards the center const centerEffectX = (window.innerWidth / 2 - blob.x) * 0.0005 * deltaTime; const centerEffectY = (window.innerHeight / 2 - blob.y) * 0.0005 * deltaTime; blob.speedX += centerEffectX; blob.speedY += centerEffectY; // Dampen speed to prevent infinite acceleration blob.speedX *= 0.99; blob.speedY *= 0.99; // Reactivity to mouse position (repulsion effect) const dx = blob.x - mouseX; const dy = blob.y - mouseY; const dist = Math.sqrt(dx * dx + dy * dy); const repelForce = Math.max(0, 1 - dist / 500) * 0.005; // Stronger repulsion closer to mouse blob.speedX += (dx / dist) * repelForce * deltaTime; blob.speedY += (dy / dist) * repelForce * deltaTime; // Pulsate size using sine wave for organic feel const currentSize = BLOB_PART_MIN_SIZE + (BLOB_PART_MAX_SIZE - BLOB_PART_MIN_SIZE) * (0.5 + 0.5 * Math.sin(blob.scalePhase + performance.now() * 0.0008 + i * 0.1)); // Subtle rotation for organic motion const rotation = Math.sin(blob.rotPhase + performance.now() * 0.0002 + i * 0.05) * 5; // Rotation up to 5 degrees blob.style.width = blob.style.height = `${currentSize}px`; // Use translate3d for GPU acceleration, subtle rotation, and vertical scaling for liquid effect blob.style.transform = `translate3d(${blob.x - currentSize / 2}px, ${blob.y - currentSize / 2}px, 0) rotate(${rotation}deg) scaleY(1.0 + ${Math.abs(Math.sin(performance.now() * 0.0003 + i))} * 0.05)`; // Animate border-radius for constantly shifting, organic blob shapes blob.style.borderRadius = `${(40 + 20 * Math.sin(performance.now() * 0.0004 + i * 0.2))}% ${(60 + 20 * Math.cos(performance.now() * 0.0005 + i * 0.3))}% ${(50 + 20 * Math.sin(performance.now() * 0.0006 + i * 0.4))}% ${(50 + 20 * Math.cos(performance.now() * 0.0007 + i * 0.5))}% / ${(50 + 20 * Math.cos(performance.now() * 0.0008 + i * 0.6))}% ${(50 + 20 * Math.sin(performance.now() * 0.0009 + i * 0.7))}% ${(40 + 20 * Math.cos(performance.now() * 0.001 + i * 0.8))}% ${(60 + 20 * Math.sin(performance.now() * 0.0011 + i * 0.9))}%`; }); } /* --- Flicker Divs (Edges) --- */ const FLICKER_DIV_COUNT = 150; const FLICKER_DIV_SIZE = 20; const flickerDivs = []; let totalFlickerDivsOpacity = 0; // Used for audio pitch calculation // Create flicker div elements and setup hover/leave behaviors for (let i = 0; i < FLICKER_DIV_COUNT; i++) { const flicker = document.createElement('div'); flicker.className = 'flicker-div'; flicker.style.width = flicker.style.height = `${FLICKER_DIV_SIZE + Math.random() * 10}px`; flicker.x = window.innerWidth / 2 + (Math.random() - 0.5) * 600; // Start around center, wider spread flicker.y = window.innerHeight / 2 + (Math.random() - 0.5) * 600; flicker.speedX = (Math.random() - 0.5) * 5; // Random movement speed flicker.speedY = (Math.random() - 0.5) * 5; flicker.targetOpacity = 0.2 + Math.random() * 0.2; // Base opacity for calculating hue flicker.isDissolving = false; // State for hover effect flicker.reappearingTimeout = null; // Mouse enter: Start dissolving effect flicker.addEventListener('mouseenter', () => { clearTimeout(flicker.reappearingTimeout); // Clear any pending reappearances if (!flicker.isDissolving) { flicker.isDissolving = true; flicker.classList.add('dissolving'); // Trigger CSS transition flicker.currentOpacity = 0; // Mark for audio calculation } }); // Mouse leave: Schedule reappearance flicker.addEventListener('mouseleave', () => { if (flicker.isDissolving) { flicker.reappearingTimeout = setTimeout(() => { flicker.isDissolving = false; flicker.classList.remove('dissolving'); flicker.currentOpacity = flicker.targetOpacity; // Reset for audio calculation }, 500 + Math.random() * 500); // Random delay to make reappearance less uniform } }); interactiveContainer.appendChild(flicker); flickerDivs.push(flicker); } // Update positions, movement, and hover states of flicker divs function updateFlickerDivs(deltaTime) { totalFlickerDivsOpacity = 0; // Reset for calculation flickerDivs.forEach((flicker, i) => { // Apply random movement flicker.x += flicker.speedX * deltaTime; flicker.y += flicker.speedY * deltaTime; // Confine flicker divs to a central area (e.g., around the main blobs) const boundsMargin = 200; // Area beyond max blob size const minX = window.innerWidth / 2 - (BLOB_PART_MAX_SIZE * 1.5) - boundsMargin; const maxX = window.innerWidth / 2 + (BLOB_PART_MAX_SIZE * 1.5) + boundsMargin; const minY = window.innerHeight / 2 - (BLOB_PART_MAX_SIZE * 1.5) - boundsMargin; const maxY = window.innerHeight / 2 + (BLOB_PART_MAX_SIZE * 1.5) + boundsMargin; // Bounce off boundaries if (flicker.x < minX || flicker.x > maxX) flicker.speedX *= -1; if (flicker.y < minY || flicker.y > maxY) flicker.speedY *= -1; // Introduce random changes in direction to prevent predictable patterns if (Math.random() < 0.005) { // 0.5% chance per frame flicker.speedX = (Math.random() - 0.5) * 5; flicker.speedY = (Math.random() - 0.5) * 5; } // Slight attraction/repulsion to mouse for a "distortion" feel const dx = flicker.x - mouseX; const dy = flicker.y - mouseY; const dist = Math.sqrt(dx * dx + dy * dy); const forceMagnitude = Math.max(0, 1 - dist / 300) * 0.005; // Influenced within 300px radius flicker.speedX += (dx / dist) * forceMagnitude * deltaTime; flicker.speedY += (dy / dist) * forceMagnitude * deltaTime; flicker.speedX *= 0.995; // Dampen speed flicker.speedY *= 0.995; // Apply transforms (translate for position, scale for dissolving) flicker.style.transform = `translate3d(${flicker.x - flicker.offsetWidth / 2}px, ${flicker.y - flicker.offsetHeight / 2}px, 0) scale(${flicker.isDissolving ? 0 : 1})`; // Add current opacity to total for audio calculation if (!flicker.isDissolving) { totalFlickerDivsOpacity += flicker.targetOpacity; } }); // Update audio frequency based on the calculated total opacity updateAudioFrequency(totalFlickerDivsOpacity); } /* --- Background Canvas (Gradient and Ripples) --- */ const ripples = []; const MAX_RIPPLES = 10; // Max number of simultaneous ripples const RIPPLE_SPEED = 0.5; // pixels per millisecond const RIPPLE_MAX_RADIUS = Math.max(window.innerWidth, window.innerHeight) / 2; // Ripples can expand up to half the screen size // Add a new ripple effect at a given position function addRipple(x, y) { ripples.push({ x, y, radius: 0, opacity: 1 }); if (ripples.length > MAX_RIPPLES) { ripples.shift(); // Remove the oldest ripple if max is reached } } // Trigger ripples on mouse movement, with a frequency limit let lastRippleTime = 0; window.addEventListener('mousemove', (e) => { if (performance.now() - lastRippleTime > 100) { // Only add a new ripple every 100ms addRipple(e.clientX, e.clientY); lastRippleTime = performance.now(); } }); // Draw the background gradient and ripple effects on the canvas function drawBackground(deltaTime) { ctx.clearRect(0, 0, backgroundCanvas.width, backgroundCanvas.height); // Clear entire canvas // Create an undulating radial gradient for the background const gradient = ctx.createRadialGradient( backgroundCanvas.width / 2 + Math.sin(performance.now() * 0.00005) * 500, /* Animate x-center */ backgroundCanvas.height / 2 + Math.cos(performance.now() * 0.00006) * 500, /* Animate y-center */ 100, /* Inner radius */ backgroundCanvas.width / 2 + Math.cos(performance.now() * 0.00004) * 400, /* Animate outer x-center */ backgroundCanvas.height / 2 + Math.sin(performance.now() * 0.00007) * 400, /* Animate outer y-center */ Math.max(backgroundCanvas.width, backgroundCanvas.height) / 1.5 /* Outer radius */ ); gradient.addColorStop(0, `rgba(5, 5, 20, 1)`); /* Deep blue/violet at center */ gradient.addColorStop(0.5, `rgba(10, 10, 50, 1)`); /* Mid-blue */ gradient.addColorStop(1, `rgba(0, 0, 10, 1)`); /* Even deeper blue at edges */ ctx.fillStyle = gradient; ctx.fillRect(0, 0, backgroundCanvas.width, backgroundCanvas.height); // Draw and update ripple effects for (let i = ripples.length - 1; i >= 0; i--) { const ripple = ripples[i]; ripple.radius += RIPPLE_SPEED * deltaTime; // Expand radius ripple.opacity = 1 - (ripple.radius / RIPPLE_MAX_RADIUS); // Fade out as it expands // Remove ripples that are fully faded or too large if (ripple.opacity <= 0 || ripple.radius >= RIPPLE_MAX_RADIUS) { ripples.splice(i, 1); continue; } ctx.beginPath(); ctx.arc(ripple.x, ripple.y, ripple.radius, 0, Math.PI * 2); ctx.strokeStyle = `rgba(150, 200, 255, ${ripple.opacity * 0.6})`; /* Luminous blue, semi-transparent */ ctx.lineWidth = 2 + ripple.radius * 0.01; // Line thickness increases with size ctx.stroke(); } } /* --- Main Animation Loop --- */ let lastTime = 0; function animate(currentTime) { const deltaTime = currentTime - lastTime; lastTime = currentTime; // Smoothly interpolate mouse position for fluid reactions mouseX += (targetMouseX - mouseX) * 0.05; mouseY += (targetMouseY - mouseY) * 0.05; // Update all dynamic elements updateBlobParts(deltaTime); updateFlickerDivs(deltaTime); drawBackground(deltaTime); // Request next animation frame requestAnimationFrame(animate); } // Start the animation loop requestAnimationFrame(animate); </script> </body> </html> ```
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Living Mass</title> <style> body, html { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background-color: #02020a; cursor: none; font-family: monospace; color: rgba(255, 255, 255, 0.4); } body { background-image: radial-gradient( circle at 50% 50%, rgba(30, 80, 120, 0.4) 0%, rgba(10, 20, 40, 0.2) 25%, #02020a 60% ); background-size: 250% 250%; animation: slow-undulate 25s infinite alternate ease-in-out; transition: background-position 0.2s ease-out; } @keyframes slow-undulate { 0% { background-position: 0% 50%; } 100% { background-position: 100% 50%; } } #liquid-container { position: absolute; top: 50%; left: 50%; width: 60vmin; height: 60vmin; /* The "metaball" or liquid effect */ filter: blur(12px) contrast(25); } #liquid-shape { position: absolute; top: 0; left: 0; width: 100%; height: 100%; animation: pulsate 12s infinite alternate ease-in-out; } @keyframes pulsate { 0% { transform: scale(0.95) rotate(0deg); border-radius: 40% 60% 70% 30% / 40% 40% 60% 50%; } 50% { border-radius: 30% 70% 40% 60% / 60% 30% 70% 40%; } 100% { transform: scale(1.05) rotate(10deg); border-radius: 60% 40% 30% 70% / 50% 60% 40% 40%; } } .shard { position: absolute; background: radial-gradient(circle, #ade8f4, #48cae4, #00b4d8); border-radius: 50%; transition: opacity 0.5s cubic-bezier(0.6, -0.28, 0.735, 0.045), transform 0.5s cubic-bezier(0.6, -0.28, 0.735, 0.045), filter 0.5s ease-out; filter: blur(0px); } .shard.dissolving { opacity: 0 !important; transform: scale(0.1) !important; filter: blur(20px); } #click-prompt { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -80px); /* Positioned above the mass */ font-size: 1.2em; text-shadow: 0 0 10px rgba(0, 180, 216, 0.5); animation: fadeInOut 3s infinite alternate; pointer-events: none; z-index: 100; } @keyframes fadeInOut { from { opacity: 0.2; } to { opacity: 0.7; } } </style> </head> <body> <div id="liquid-container"> <div id="liquid-shape"></div> </div> <div id="click-prompt">Interact to awaken</div> <script> const container = document.getElementById('liquid-shape'); const promptEl = document.getElementById('click-prompt'); const NUM_SHARDS = 80; const shards = []; let audioContext; let oscillator; let gainNode; let isAudioInitialized = false; function initAudio() { if (isAudioInitialized) return; try { audioContext = new (window.AudioContext || window.webkitAudioContext)(); oscillator = audioContext.createOscillator(); gainNode = audioContext.createGain(); oscillator.type = 'sine'; oscillator.frequency.setValueAtTime(50, audioContext.currentTime); // Start at a low 50Hz hum gainNode.gain.setValueAtTime(0.15, audioContext.currentTime); // Keep volume low oscillator.connect(gainNode); gainNode.connect(audioContext.destination); oscillator.start(); isAudioInitialized = true; promptEl.style.transition = 'opacity 0.5s'; promptEl.style.opacity = '0'; setTimeout(() => promptEl.style.display = 'none', 500); } catch (e) { console.error("Web Audio API is not supported in this browser or could not be initialized.", e); promptEl.textContent = "Audio could not be started."; } } // Generate the overlapping semi-transparent divs (shards) for (let i = 0; i < NUM_SHARDS; i++) { const shard = document.createElement('div'); shard.classList.add('shard'); const size = Math.random() * 25 + 35; // size in % of container const angle = Math.random() * 2 * Math.PI; const radius = (Math.random() * Math.random()) * 45; // distance from center in %, clustered towards middle shard.style.width = `${size}%`; shard.style.height = `${size}%`; // Position relative to the center of the container shard.style.left = `${50 + radius * Math.cos(angle)}%`; shard.style.top = `${50 + radius * Math.sin(angle)}%`; shard.style.transform = `translate(-50%, -50%) scale(1)`; // Give each shard its own base opacity const baseOpacity = Math.random() * 0.4 + 0.5; shard.dataset.baseOpacity = baseOpacity; shard.style.opacity = baseOpacity; container.appendChild(shard); shards.push(shard); // Add hover listener to make it dissolve shard.addEventListener('mouseover', () => { if (!shard.classList.contains('dissolving')) { shard.classList.add('dissolving'); // Randomly decide when it should reappear setTimeout(() => { shard.classList.remove('dissolving'); }, 1500 + Math.random() * 2000); } }); } // Ripple effect for background on mouse move window.addEventListener('mousemove', (e) => { if (!isAudioInitialized) { initAudio(); } const { clientX, clientY } = e; const { innerWidth, innerHeight } = window; const xPercent = (clientX / innerWidth) * 100; const yPercent = (clientY / innerHeight) * 100; document.body.style.backgroundPosition = `${xPercent}% ${yPercent}%`; }); // Add a click listener as a fallback to initialize audio, which is required by many browsers. window.addEventListener('click', initAudio, { once: true }); // Main animation loop to update audio pitch based on transparency function updateAudioPitch() { if (!isAudioInitialized) { requestAnimationFrame(updateAudioPitch); return; } let totalOpacity = 0; shards.forEach(shard => { // getComputedStyle is crucial for getting the opacity value during a CSS transition totalOpacity += parseFloat(window.getComputedStyle(shard).opacity); }); const avgOpacity = totalOpacity / shards.length; // Map the average opacity to a frequency range. // A lower average opacity (more dissolved shards) should result in a higher pitch. // Let's assume the base average opacity is ~0.7. Range could be [0.3, 0.7]. // We map this range to a frequency range of [120Hz, 50Hz]. const normalizedDissolution = Math.max(0, Math.min(1, (0.7 - avgOpacity) / 0.4)); const newFreq = 50 + normalizedDissolution * 70; // Base: 50Hz, Max change: +70Hz // Use setTargetAtTime for a smooth, exponential transition to the new frequency oscillator.frequency.setTargetAtTime(newFreq, audioContext.currentTime, 0.1); requestAnimationFrame(updateAudioPitch); } updateAudioPitch(); </script> </body> </html> ```
```html <!DOCTYPE html> <html> <head> <style> body { margin: 0; overflow: hidden; background: #000; } .container { position: relative; width: 100vw; height: 100vh; } .blob { position: absolute; border-radius: 50%; background: rgba(255, 255, 255, 0.2); mix-blend-mode: screen; transition: transform 0.3s ease; } .ripple { position: absolute; border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 50%; pointer-events: none; animation: ripple 2s ease-out forwards; } @keyframes ripple { 0% { transform: scale(0); opacity: 1; } 100% { transform: scale(10); opacity: 0; } } </style> </head> <body> <div class="container" id="container"></div> <script> const container = document.getElementById('container'); const blobs = []; const numBlobs = 50; let audioContext, oscillator, gainNode; // Initialize audio function initAudio() { audioContext = new (window.AudioContext || window.webkitAudioContext)(); oscillator = audioContext.createOscillator(); gainNode = audioContext.createGain(); oscillator.type = 'sine'; oscillator.connect(gainNode); gainNode.connect(audioContext.destination); oscillator.start(); } // Create blobs for(let i = 0; i < numBlobs; i++) { const blob = document.createElement('div'); blob.className = 'blob'; blob.style.width = `${Math.random() * 100 + 50}px`; blob.style.height = blob.style.width; blob.style.left = `${Math.random() * 100}%`; blob.style.top = `${Math.random() * 100}%`; blob.style.opacity = Math.random() * 0.5 + 0.1; container.appendChild(blob); blobs.push(blob); } // Mouse movement handler document.addEventListener('mousemove', (e) => { const x = e.clientX / window.innerWidth; const y = e.clientY / window.innerHeight; // Update background gradient document.body.style.background = `radial-gradient(circle at ${x * 100}% ${y * 100}%, #1a0033, #000)`; // Create ripple const ripple = document.createElement('div'); ripple.className = 'ripple'; ripple.style.left = `${e.clientX}px`; ripple.style.top = `${e.clientY}px`; ripple.style.width = '20px'; ripple.style.height = '20px'; container.appendChild(ripple); setTimeout(() => ripple.remove(), 2000); // Update blobs blobs.forEach(blob => { const blobX = parseFloat(blob.style.left) / 100; const blobY = parseFloat(blob.style.top) / 100; const dist = Math.sqrt(Math.pow(x - blobX, 2) + Math.pow(y - blobY, 2)); const scale = 1 + (1 - dist) * 0.5; blob.style.transform = `scale(${scale})`; // Update audio frequency if(dist < 0.3) { const freq = 100 + (1 - dist) * 200; oscillator.frequency.setValueAtTime(freq, audioContext.currentTime); } }); }); // Initialize on click document.addEventListener('click', () => { if(!audioContext) initAudio(); }); </script> </body> </html> ``` This creates a dynamic liquid-like experience with: 1. Semi-transparent blobs that react to mouse movement 2. Expanding ripple effects on hover 3. Background gradient that follows mouse position 4. Audio feedback that changes pitch based on proximity to blobs 5. No navigation or text elements 6. All animations handled via CSS transitions and JS-driven transformations The blobs will pulsate and distort based on mouse position, while the audio provides additional sensory feedback. The ripples create a water-like effect when interacting with the surface.
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Liquid Pulsating Shape</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> html, body { height: 100%; margin: 0; overflow: hidden; background: #151922; } body { width: 100vw; height: 100vh; padding: 0; box-sizing: border-box; } .shape-container { position: absolute; width: 100vw; height: 100vh; top: 0; left: 0; pointer-events: none; } .liquid-blob { position: absolute; border-radius: 50%; pointer-events: auto; will-change: transform, opacity, filter; transition: filter 0.4s cubic-bezier(0.57,0.11,0.29,0.99), opacity 0.3s; box-shadow: 0 0 50px 0 rgba(30,36,80,0.22), 0 0 120px 60px rgba(46,70,170,0.09); filter: blur(0.5px) brightness(1); z-index:2; } body::before { content: ""; position: absolute; left:0; top:0; right:0; bottom:0; z-index: 0; pointer-events: none; /* This is replaced by canvas.js for dynamic background */ } canvas#rippling-bg { position: fixed; left:0; top:0; z-index: 1; width: 100vw; height: 100vh; pointer-events: none; display: block; background: none; mix-blend-mode: lighten; } </style> </head> <body> <canvas id="rippling-bg"></canvas> <div class="shape-container"></div> <audio id="humming-sound" loop autoplay></audio> <script> // ----- Configurations ----- const NUM_BLOBS = 30; const MIN_RADIUS = 60, MAX_RADIUS = 170; const SHAPE_CENTER_AMPLITUDE = 10; const PULSE_SPEED = 0.9; const FADE_OPACITY = 0.28; const EDGE_OPACITY = 0.12; const DISSOLVE_OPACITY = 0.02; // ----- Utility ----- function lerp(a, b, p) { return a + (b - a) * p; } // ----- Dynamic Background ----- const canvas = document.getElementById('rippling-bg'); const ctx = canvas.getContext('2d'); let w = window.innerWidth, h = window.innerHeight; let mx = w/2, my = h/2; function resizeCanvas() { w = window.innerWidth; h = window.innerHeight; canvas.width = w; canvas.height = h; } window.addEventListener('resize', resizeCanvas); resizeCanvas(); // Create a slow undulating background gradient that's rippled by mouse movement let t = 0, ripples = []; canvas.addEventListener('pointermove', e=>{ mx = e.clientX; my = e.clientY; ripples.push({x: mx, y: my, r: 0, a: 1, t: performance.now()}); if (ripples.length > 12) ripples.shift(); }); function drawGradientBG(dt) { let img = ctx.createImageData(w, h); let px = img.data; const grad1 = [ { r: 40, g:25, b:155 }, { r: 40, g:125, b:185 }, { r: 50, g:180, b:240 } ]; const grad2 = [ { r: 100, g:25, b:215 }, { r: 155, g:225, b:220 }, { r: 210, g:220, b:160 } ]; // Gradient wave params let dx = Math.sin(dt*0.13), dy = Math.cos(dt*0.09+2); for (let y = 0; y < h; y++) { let yp = y/h; for (let x = 0; x < w; x++) { let xp = x/w, blend = (Math.sin(dt*0.021 + x*0.011 + y*0.009) + 1)/2; let g1 = grad1.map(c=>lerp(c.r, c.b, yp)); let r = Math.floor(lerp(grad1[0].r, grad1[2].r, yp)), g = Math.floor(lerp(grad1[0].g, grad1[2].g, yp)), b = Math.floor(lerp(grad1[0].b, grad1[2].b, yp)); let baseR = lerp(r, grad2[0].r, blend*blend*0.8); let baseG = lerp(g, grad2[0].g, blend*0.55); let baseB = lerp(b, grad2[0].b, 0.3*blend); // Rippling let rippleSum=0; let distAlpha = 0; ripples.forEach(r=>{ let dx = x - r.x, dy = y - r.y, dist = Math.sqrt(dx*dx+dy*dy); let a = r.a*Math.exp(-dist/380); distAlpha += 0.25*a; let ripple = Math.sin(dist/13 - (dt - r.t)*0.013) * a; rippleSum += ripple; }); baseR += 46*rippleSum + distAlpha*66; baseG += 95*rippleSum; baseB += 125*rippleSum + distAlpha*86; let i = (y*w + x)*4; px[i]=Math.min(255,Math.max(0,baseR)); px[i+1]=Math.min(255,Math.max(0,baseG)); px[i+2]=Math.min(255,Math.max(0,baseB)); px[i+3]=255; } } ctx.putImageData(img,0,0); } // ------ Shape Blobs ----- const shapeContainer = document.querySelector('.shape-container'); let blobs = []; let baseHue = Math.floor(lerp(200, 260, Math.random())); for (let i=0; i<NUM_BLOBS; ++i) { let d = document.createElement('div'); d.className = "liquid-blob"; shapeContainer.appendChild(d); let angle = Math.PI * 2 * i / NUM_BLOBS; let phaseOffset = Math.random()*Math.PI*2; let radius = lerp(MIN_RADIUS, MAX_RADIUS, 0.5+0.4*Math.sin(angle+phaseOffset)); let blob = { el: d, baseRadius: radius, angle: angle, phaseOffset: phaseOffset, dissolve: false, dissolveTime: 0, dissolveDir: 1, transpar: lerp(EDGE_OPACITY, FADE_OPACITY, Math.sin(angle)*0.3+0.7), hover: false, pos: {x:0,y:0,r:radius}, } blobs.push(blob); // Mouse handling d.addEventListener('pointerover', ()=>{ blob.dissolve = true; blob.dissolveTime = 0; blob.dissolveDir = 1; blob.hover = true; }); d.addEventListener('pointerout', ()=>{ blob.dissolveDir = -1; blob.hover = false; }); } // ---- Sound Generation (Web Audio) ---- // "Humming" base, modulated by transparency density const AudioCtx = window.AudioContext || window.webkitAudioContext; const ctxAudio = new AudioCtx(); const humOsc = ctxAudio.createOscillator(); const humGain = ctxAudio.createGain(); humOsc.type = 'triangle'; humOsc.frequency.value = 41; // Hz, base hum humGain.gain.value = 0.16; humOsc.connect(humGain).connect(ctxAudio.destination); humOsc.start(); window.onclick = ()=>{ // resume audio if browser blocks autoplay if (ctxAudio.state !== 'running') ctxAudio.resume(); } // ---- Animation Loop ----- function render(dt) { t += 0.017; // Move the center subtly let centerX = w/2 + (Math.sin(t*0.58)*SHAPE_CENTER_AMPLITUDE + Math.sin(t*0.13)*14); let centerY = h/2 + (Math.cos(t*0.45)*SHAPE_CENTER_AMPLITUDE + Math.sin(t*0.2)*12); // Undulating shape radius multiplier let globPulse = lerp(1,1.15, (Math.sin(t*PULSE_SPEED)+1)/2); // Update blobs let totalFade=0; blobs.forEach((b, i)=>{ // Blob angle modulated let pulse = Math.sin(t*0.5 + b.phaseOffset)*0.8+0.8; let localRad = b.baseRadius * globPulse * (1 + 0.10*pulse); // Position blobs so their centers are on an undulating curve let theta = b.angle + Math.sin(t*0.32 + b.angle)*0.18 + Math.sin(t*0.09+2-b.angle)*0.04; let edgeX = centerX + Math.cos(theta) * (220+localRad); let edgeY = centerY + Math.sin(theta) * (220+localRad); // "Liquid" effect: add some noise let n = Math.sin(t*0.8 + i)*6 + Math.cos(t*0.21 + i*1.2)*7; edgeX += n; edgeY += n*0.4; let currentTranspar = b.transpar; if (b.dissolve) { b.dissolveTime += 0.018*b.dissolveDir; currentTranspar = lerp(b.transpar, DISSOLVE_OPACITY, Math.min(b.dissolveTime,1)); if (b.dissolveDir < 0 && b.dissolveTime <= 0) b.dissolve = false; } else { b.dissolveTime = 0; } let opacity = Math.max(0, Math.min(1, currentTranspar + Math.random()*0.02)); totalFade += opacity; // CSS b.el.style.opacity = opacity; b.el.style.left = (edgeX-localRad) + "px"; b.el.style.top = (edgeY-localRad) + "px"; b.el.style.width = (localRad*2) + "px"; b.el.style.height = (localRad*2) + "px"; // Color blending let blobHue = (baseHue + Math.sin(theta*2+t*0.12)*30) % 360; let brightness = lerp(0.84,1.22, (Math.cos(theta-t*0.15)+1)/2); b.el.style.background = `radial-gradient(ellipse at 55% 40%, hsla(${blobHue},90%,63%,0.63), hsla(${(blobHue+60)%360},78%,42%,0.33) 70%, hsla(${(blobHue+40)%360},100%,11%,0.01) 100%)`; b.el.style.filter = `blur(${lerp(1,6, 1-opacity)}px) brightness(${brightness})`; b.pos.x = edgeX; b.pos.y = edgeY; b.pos.r = localRad; }); // Sound: more overlap = denser, higher pitch let densityNorm = lerp(0.26,1.15, totalFade/blobs.length); let freq = lerp(29,83, densityNorm); let vol = lerp(0.13, 0.23, densityNorm); humOsc.frequency.value = freq; humGain.gain.value = vol; drawGradientBG(t); requestAnimationFrame(render); } render(performance.now()); </script> </body> </html> ```
Here is a complete HTML file that creates a webpage with a pulsating, liquid-like shape, constantly shifting between forms. The edges are made of overlapping, semi-transparent divs that flicker and dissolve when the user hovers over them. The background is a slow, undulating gradient that reacts to the user’s movements, creating ripples that distort the divs. The page also emits a low, humming sound that changes pitch based on the density of the divs’ transparency. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Liquid Shape</title> <style> body { margin: 0; padding: 0; overflow: hidden; background: linear-gradient(45deg, #000000, #1a1a2e, #16213e); background-size: 400% 400%; animation: gradientBG 15s ease infinite; height: 100vh; display: flex; justify-content: center; align-items: center; } @keyframes gradientBG { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } } .container { position: relative; width: 500px; height: 500px; filter: url('#ripple'); } .shape { position: absolute; width: 100%; height: 100%; border-radius: 60% 40% 30% 70%/60% 30% 70% 40%; background: rgba(100, 200, 255, 0.3); transition: all 0.5s ease; animation: pulse 5s ease infinite; } @keyframes pulse { 0% { border-radius: 60% 40% 30% 70%/60% 30% 70% 40%; } 50% { border-radius: 30% 60% 70% 40%/50% 60% 30% 80%; } 100% { border-radius: 60% 40% 30% 70%/60% 30% 70% 40%; } } .shape:hover { opacity: 0; transition: opacity 0.5s ease; } </style> </head> <body> <div class="container" id="container"> <div class="shape" id="shape1"></div> <div class="shape" id="shape2"></div> <div class="shape" id="shape3"></div> </div> <svg width="0" height="0" style="position: absolute;"> <filter id="ripple"> <feTurbulence type="fractalNoise" baseFrequency="0.01 0.02" numOctaves="1" result="warp" /> <feDisplacementMap xChannelSelector="R" yChannelSelector="G" scale="30" in="SourceGraphic" in2="warp" /> </filter> </svg> <script> const container = document.getElementById('container'); const shapes = document.querySelectorAll('.shape'); document.addEventListener('mousemove', (e) => { const x = e.clientX / window.innerWidth; const y = e.clientY / window.innerHeight; container.style.filter = `url('#ripple')`; document.body.style.backgroundPosition = `${x * 100}% ${y * 100}%`; shapes.forEach(shape => { const rect = shape.getBoundingClientRect(); const shapeX = (rect.left + rect.right) / 2; const shapeY = (rect.top + rect.bottom) / 2; const distance = Math.sqrt((e.clientX - shapeX) ** 2 + (e.clientY - shapeY) ** 2); const opacity = Math.min(1, distance / 300); shape.style.opacity = opacity; }); }); // Audio context for the humming sound const AudioContext = window.AudioContext || window.webkitAudioContext; const audioContext = new AudioContext(); function playHummingSound() { const oscillator = audioContext.createOscillator(); const gainNode = audioContext.createGain(); oscillator.type = 'sine'; oscillator.frequency.setValueAtTime(100, audioContext.currentTime); gainNode.gain.setValueAtTime(0.1, audioContext.currentTime); oscillator.connect(gainNode); gainNode.connect(audioContext.destination); oscillator.start(); // Change pitch based on the density of the divs' transparency setInterval(() => { const totalOpacity = Array.from(shapes).reduce((sum, shape) => sum + parseFloat(shape.style.opacity || 1), 0); const averageOpacity = totalOpacity / shapes.length; const frequency = 50 + (averageOpacity * 200); oscillator.frequency.setValueAtTime(frequency, audioContext.currentTime); }, 100); } // Start the sound when the user interacts with the page document.addEventListener('click', () => { audioContext.resume().then(() => { playHummingSound(); }); }); </script> </body> </html> ``` This code creates a webpage with a liquid-like shape that constantly shifts and pulsates. The shapes react to the user's mouse movements, and the background gradient undulates slowly. The page also emits a low, humming sound that changes pitch based on the density of the divs’ transparency.
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Living Mass</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { overflow: hidden; cursor: none; background: radial-gradient(circle at 50% 50%, #1a0033, #000011, #330022); background-size: 200% 200%; animation: breathe 8s ease-in-out infinite; height: 100vh; } @keyframes breathe { 0%, 100% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } } .container { position: relative; width: 100%; height: 100vh; perspective: 1000px; } .shape { position: absolute; border-radius: 50%; background: radial-gradient(circle, rgba(150,75,255,0.3), rgba(255,100,255,0.1)); animation: morph 6s ease-in-out infinite; transition: all 0.3s ease; backdrop-filter: blur(2px); border: 1px solid rgba(255,255,255,0.1); } .shape:hover { opacity: 0.1 !important; transform: scale(1.5) !important; animation-duration: 0.5s; } @keyframes morph { 0% { border-radius: 50% 40% 60% 30%; transform: rotate(0deg) scale(1); } 25% { border-radius: 30% 60% 40% 70%; transform: rotate(90deg) scale(1.2); } 50% { border-radius: 60% 30% 70% 40%; transform: rotate(180deg) scale(0.8); } 75% { border-radius: 40% 70% 30% 60%; transform: rotate(270deg) scale(1.1); } 100% { border-radius: 50% 40% 60% 30%; transform: rotate(360deg) scale(1); } } .pulse { animation: pulse 4s ease-in-out infinite; } @keyframes pulse { 0%, 100% { opacity: 0.8; filter: hue-rotate(0deg); } 50% { opacity: 0.3; filter: hue-rotate(180deg); } } .ripple { position: absolute; border-radius: 50%; background: radial-gradient(circle, transparent, rgba(255,255,255,0.1), transparent); pointer-events: none; animation: ripple 2s ease-out forwards; } @keyframes ripple { 0% { width: 0; height: 0; opacity: 1; } 100% { width: 300px; height: 300px; opacity: 0; } } </style> </head> <body> <div class="container" id="container"></div> <script> // Audio context for the humming sound let audioContext; let oscillator; let gainNode; function initAudio() { audioContext = new (window.AudioContext || window.webkitAudioContext)(); oscillator = audioContext.createOscillator(); gainNode = audioContext.createGain(); oscillator.connect(gainNode); gainNode.connect(audioContext.destination); oscillator.type = 'sine'; oscillator.frequency.setValueAtTime(80, audioContext.currentTime); gainNode.gain.setValueAtTime(0.1, audioContext.currentTime); oscillator.start(); } const container = document.getElementById('container'); const shapes = []; let mouseX = window.innerWidth / 2; let mouseY = window.innerHeight / 2; let totalOpacity = 0; // Create liquid shapes function createShape() { const shape = document.createElement('div'); shape.className = 'shape'; const size = Math.random() * 200 + 50; const x = Math.random() * (window.innerWidth - size); const y = Math.random() * (window.innerHeight - size); const opacity = Math.random() * 0.7 + 0.1; shape.style.width = size + 'px'; shape.style.height = size + 'px'; shape.style.left = x + 'px'; shape.style.top = y + 'px'; shape.style.opacity = opacity; shape.style.animationDelay = Math.random() * 6 + 's'; if (Math.random() > 0.5) { shape.classList.add('pulse'); } // Add dissolve effect on hover shape.addEventListener('mouseenter', () => { shape.style.filter = 'blur(10px)'; }); shape.addEventListener('mouseleave', () => { shape.style.filter = 'blur(2px)'; }); container.appendChild(shape); shapes.push({element: shape, opacity: opacity}); // Remove shape after some time and create a new one setTimeout(() => { if (shape.parentNode) { shape.parentNode.removeChild(shape); const index = shapes.findIndex(s => s.element === shape); if (index > -1) shapes.splice(index, 1); } createShape(); }, Math.random() * 10000 + 5000); } // Create ripples on mouse movement function createRipple(x, y) { const ripple = document.createElement('div'); ripple.className = 'ripple'; ripple.style.left = (x - 150) + 'px'; ripple.style.top = (y - 150) + 'px'; container.appendChild(ripple); setTimeout(() => { if (ripple.parentNode) { ripple.parentNode.removeChild(ripple); } }, 2000); } // Update background gradient based on mouse position function updateBackground() { const x = (mouseX / window.innerWidth) * 100; const y = (mouseY / window.innerHeight) * 100; document.body.style.background = `radial-gradient(circle at ${x}% ${y}%, rgba(${Math.sin(Date.now() * 0.001) * 50 + 100}, ${Math.cos(Date.now() * 0.001) * 50 + 100}, ${Math.sin(Date.now() * 0.002) * 100 + 155}, 0.8), rgba(${Math.cos(Date.now() * 0.0015) * 30 + 50}, ${Math.sin(Date.now() * 0.0015) * 30 + 50}, ${Math.cos(Date.now() * 0.001) * 50 + 100}, 0.3))`; } // Update audio based on opacity density function updateAudio() { if (oscillator && gainNode) { totalOpacity = shapes.reduce((sum, shape) => { return sum + parseFloat(shape.element.style.opacity || 0); }, 0); const frequency = 80 + (totalOpacity * 20); const volume = Math.min(0.15, totalOpacity * 0.02); oscillator.frequency.exponentialRampToValueAtTime( frequency, audioContext.currentTime + 0.1 ); gainNode.gain.exponentialRampToValueAtTime( Math.max(0.01, volume), audioContext.currentTime + 0.1 ); } } // Mouse movement handler document.addEventListener('mousemove', (e) => { mouseX = e.clientX; mouseY = e.clientY; // Create ripples occasionally if (Math.random() > 0.95) { createRipple(mouseX, mouseY); } // Distort nearby shapes shapes.forEach(shape => { const rect = shape.element.getBoundingClientRect(); const centerX = rect.left + rect.width / 2; const centerY = rect.top + rect.height / 2; const distance = Math.sqrt( Math.pow(mouseX - centerX, 2) + Math.pow(mouseY - centerY, 2) ); if (distance < 200) { const distortion = (200 - distance) / 200; shape.element.style.transform = `scale(${1 + distortion * 0.3}) rotate(${distortion * 45}deg)`; } }); }); // Click to initialize audio (required by browsers) document.addEventListener('click', () => { if (!audioContext) { initAudio(); } }, { once: true }); // Animation loop function animate() { updateBackground(); updateAudio(); // Add organic movement to shapes shapes.forEach((shape, index) => { const time = Date.now() * 0.001; const x = parseFloat(shape.element.style.left); const y = parseFloat(shape.element.style.top); shape.element.style.left = (x + Math.sin(time + index) * 0.5) + 'px'; shape.element.style.top = (y + Math.cos(time + index * 0.7) * 0.3) + 'px'; }); requestAnimationFrame(animate); } // Initialize for (let i = 0; i < 15; i++) { createShape(); } animate(); // Handle window resize window.addEventListener('resize', () => { mouseX = window.innerWidth / 2; mouseY = window.innerHeight / 2; }); </script> </body> </html> ```
Certainly! Here’s a self-contained HTML file that achieves the description of a webpage with a pulsating, liquid-like shape, semi-transparent divs that flicker and dissolve on hover, an undulating gradient background that reacts to user movements, and a low, humming sound that changes pitch based on the density of the divs’ transparency. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Liquid Shape Webpage</title> <style> body, html { margin: 0; padding: 0; overflow: hidden; height: 100%; background: linear-gradient(135deg, #ffcc00 25%, #ff6600 25%, #ff6600 50%, #ffcc00 50%, #ffcc00 75%, #ff6600 75%, #ff6600 100%); background-size: 200% 200%; animation: gradientAnimation 10s ease infinite; } .shape { position: absolute; width: 100px; height: 100px; border-radius: 50%; background: rgba(255, 255, 255, 0.5); border: 1px solid rgba(255, 255, 255, 0.2); animation: pulsate 5s infinite; transition: opacity 0.5s, transform 0.5s; } @keyframes gradientAnimation { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } } @keyframes pulsate { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.2); } } </style> </head> <body> <div id="shapes-container"></div> <audio id="audio" autoplay loop> Your browser does not support the audio element. </audio> <script> const createShapes = () => { const container = document.getElementById('shapes-container'); for (let i = 0; i < 20; i++) { const shape = document.createElement('div'); shape.className = 'shape'; shape.style.left = `${Math.random() * 100}%`; shape.style.top = `${Math.random() * 100}%`; container.appendChild(shape); shape.addEventListener('mouseover', () => { shape.style.opacity = 0.2; }); shape.addEventListener('mouseout', () => { shape.style.opacity = 0.5; }); } }; const adjustAudioPitch = () => { const shapes = document.querySelectorAll('.shape'); let totalOpacity = 0; shapes.forEach(shape => { totalOpacity += parseFloat(shape.style.opacity); }); const averageOpacity = totalOpacity / shapes.length; const audio = document.getElementById('audio'); audio.playbackRate = 1 + (1 - averageOpacity) / 2; }; document.addEventListener('mousemove', (e) => { const container = document.getElementById('shapes-container'); container.style.backgroundPosition = `${e.clientX / window.innerWidth * 100}% ${e.clientY / window.innerHeight * 100}%`; adjustAudioPitch(); }); createShapes(); // Create a simple humming sound const audioContext = new (window.AudioContext || window.webkitAudioContext)(); const oscillator = audioContext.createOscillator(); const gainNode = audioContext.createGain(); oscillator.connect(gainNode); gainNode.connect(audioContext.destination); oscillator.type = 'sine'; oscillator.frequency.setValueAtTime(100, audioContext.currentTime); gainNode.gain.setValueAtTime(0.1, audioContext.currentTime); oscillator.start(); document.getElementById('audio').src = URL.createObjectURL(audioContext.createMediaStreamDestination().stream); </script> </body> </html> ``` The code above includes the necessary HTML, CSS, and JavaScript to create an interactive and visually engaging webpage that fulfills the provided specifications. The humming sound effect is generated using the Web Audio API.
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Liquid-like Morphing Shape</title> <style> /* Reset and base styles */ body { margin: 0; overflow: hidden; height: 100vh; background: linear-gradient(270deg, #1e3c72, #2a5298); font-family: sans-serif; } /* Container for the shape */ #container { position: absolute; top: 50%; left: 50%; width: 600px; height: 600px; transform: translate(-50%, -50%); perspective: 1000px; } /* Canvas for undulating gradient background */ #background-canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 0; } /* Divs that form the edges of the shape */ #shapeDivs { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; } /* Style for each edge div */ .edge { position: absolute; border-radius: 50%; width: 120px; height: 120px; border: 4px solid rgba(255,255,255,0.3); background-color: rgba(255,255,255,0.2); opacity: 0.6; mix-blend-mode: overlay; pointer-events: none; animation: flicker 2s infinite ease-in-out; } /* Flicker animation for edge flickering/dissolving effect */ @keyframes flicker { 0%, 100% { opacity: 0.6; } 50% { opacity: 0.2; } 75% { opacity: 0.4; } } /* Optional: smooth transition for opacity on hover */ .shape-edge:hover { opacity: 0; transition: opacity 0.3s ease; } </style> </head> <body> <div id="container"> <canvas id="background-canvas"></canvas> <div id="shapeDivs"></div> </div> <script> // JavaScript for generating the shape, undulating background, hover effects, and sound // --- Shape Configuration --- const NUM_EDGES = 20; // Number of overlapping divs const shapeDivsContainer = document.getElementById('shapeDivs'); const shapeDivs = []; // Helper function to create a div with class 'edge' function createEdgeDiv() { const div = document.createElement('div'); div.className = 'edge'; shapeDivsContainer.appendChild(div); return div; } // Initialize edges with random positions for (let i=0; i<NUM_EDGES; i++) { const div = createEdgeDiv(); div.style.top = Math.random() * 100 + '%'; div.style.left = Math.random() * 100 + '%'; // Assign random size for variation const size = 80 + Math.random() * 40; // px div.style.width = size + 'px'; div.style.height = size + 'px'; // Store for updates shapeDivs.push({ element: div, angle: Math.random() * Math.PI*2, radius: 50 + Math.random() * 150, speed: 0.002 + Math.random() * 0.004, size: size, flickerOffset: Math.random() * Math.PI * 2 }); } // Animate shape by updating positions in a circular, morphing pattern function animateShape() { const now = Date.now(); shapeDivs.forEach((shape, index) => { shape.angle += shape.speed; const x = 50 + Math.cos(shape.angle) * shape.radius; const y = 50 + Math.sin(shape.angle) * shape.radius; shape.element.style.top = y + '%'; shape.element.style.left = x + '%'; // Optional: also vary size subtly for more fluid effect const sizeVariance = Math.sin(now * shape.speed + shape.flickerOffset) * 2; shape.element.style.width = (shape.size + sizeVariance) + 'px'; shape.element.style.height = (shape.size + sizeVariance) + 'px'; // Flicker effect can be enhanced by adjusting opacity based on sine wave const flickerAlpha = 0.4 + Math.sin(now * shape.speed + shape.flickerOffset) * 0.2; shape.element.style.borderColor = `rgba(255,255,255,${0.3 + flickerAlpha})`; shape.element.style.backgroundColor = `rgba(255,255,255,${0.2 + flickerAlpha/2})`; }); requestAnimationFrame(animateShape); } // --- Undulating Background --- const bgCanvas = document.getElementById('background-canvas'); const ctx = bgCanvas.getContext('2d'); let width = window.innerWidth; let height = window.innerHeight; bgCanvas.width = width; bgCanvas.height = height; // Handle resize window.addEventListener('resize', () => { width = window.innerWidth; height = window.innerHeight; bgCanvas.width = width; bgCanvas.height = height; }); // Variables for gradient undulation let hueOffset = 0; function drawBackground() { const imageData = ctx.createImageData(width, height); const data = imageData.data; for(let y=0; y<height; y++) { for(let x=0; x<width; x++) { const index = (y*width + x)*4; // Calculate offset for ripple effect based on position and time const ripple = Math.sin((x/width)*10 + hueOffset + Math.sin(y/height*5 + hueOffset*2)*2); // Use ripple to shift hue const hue = (120 + ripple * 30 + hueOffset*10) % 360; // Convert hue to RGB const rgb = hslToRgb(hue/360, 0.6, 0.4); data[index] = rgb[0]; data[index+1] = rgb[1]; data[index+2] = rgb[2]; data[index+3] = 255; // full opacity } } ctx.putImageData(imageData, 0, 0); hueOffset += 0.005; // slow undulation requestAnimationFrame(drawBackground); } // Helper function to convert HSL to RGB function hslToRgb(h, s, l){ let r, g, b; if(s === 0){ r = g = b = l; // achromatic } else { const hue2rgb = function hue2rgb(p, q, t){ if(t < 0) t += 1; if(t > 1) t -= 1; if(t < 1/6) return p + (q - p) * 6 * t; if(t < 1/2) return q; if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; return p; } const q = l < 0.5 ? l * (1 + s) : l + s - l * s; const p = 2 * l - q; r = hue2rgb(p, q, h + 1/3); g = hue2rgb(p, q, h); b = hue2rgb(p, q, h - 1/3); } return [Math.round(r*255), Math.round(g*255), Math.round(b*255)]; } // --- User Interaction & Ripples --- let rippleX = null; let rippleY = null; window.addEventListener('mousemove', (e) => { rippleX = e.clientX; rippleY = e.clientY; applyRipple(rippleX, rippleY); }); function applyRipple(x, y) { // Convert to relative position const relX = x / width; const relY = y / height; // For aesthetic, add ripple distortion by shifting hue or applying CSS transforms if needed // Here, we'll just update background based on movement hueOffset += Math.sin(relX * Math.PI * 2 + relY * Math.PI * 2) * 0.1; } // --- Sound Production --- let audioContext = null; let oscillator = null; let gainNode = null; function initAudio() { try { audioContext = new (window.AudioContext || window.webkitAudioContext)(); oscillator = audioContext.createOscillator(); gainNode = audioContext.createGain(); oscillator.type = 'sine'; oscillator.frequency.setValueAtTime(440, audioContext.currentTime); // default pitch gainNode.gain.value = 0.02; // low volume oscillator.connect(gainNode); gainNode.connect(audioContext.destination); oscillator.start(); } catch(e) { console.warn('Web Audio API not supported', e); } } function updateSound() { if (!audioContext || !oscillator || !gainNode) return; // Change pitch based on average transparency of divs const totalOpacity = shapeDivs.reduce((sum, shape) => { const opacity = parseFloat(window.getComputedStyle(shape.element).opacity); return sum + opacity; }, 0); const avgOpacity = totalOpacity / shapeDivs.length; // Map opacity to pitch: more opaque -> higher pitch const minFreq = 200; const maxFreq = 1000; const freq = minFreq + (maxFreq - minFreq) * avgOpacity; oscillator.frequency.setValueAtTime(freq, audioContext.currentTime); } // Launch audio context on user interaction window.addEventListener('click', () => { if (!audioContext || audioContext.state === 'suspended') { initAudio(); } }); // --- Animation Loop --- function animate() { animateShape(); drawBackground(); updateSound(); requestAnimationFrame(animate); } // Start animation animate(); </script> </body> </html> ```
Here's an HTML implementation that creates this liquid-like interactive experience: ```html <!DOCTYPE html> <html> <head> <title>Liquid Form</title> <style> body { margin: 0; overflow: hidden; background: #000; cursor: none; } #liquid-canvas { position: absolute; width: 100%; height: 100%; } .blob { position: absolute; border-radius: 50%; mix-blend-mode: screen; opacity: 0.7; transition: all 0.5s ease-out; pointer-events: none; filter: blur(5px); } #audio-container { position: absolute; bottom: 20px; right: 20px; opacity: 0.3; } </style> </head> <body> <div id="liquid-canvas"></div> <div id="audio-container"> <audio id="hum" loop> <source src="https://assets.mixkit.co/active_storage/sfx/2454/2454-preview.mp3" type="audio/mpeg"> </audio> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <script> document.addEventListener('DOMContentLoaded', () => { const canvas = document.getElementById('liquid-canvas'); const container = document.body; let width = window.innerWidth; let height = window.innerHeight; // Create audio context const audio = document.getElementById('hum'); audio.volume = 0.2; audio.play(); // Create blobs const blobCount = 20; const blobs = []; for (let i = 0; i < blobCount; i++) { const blob = document.createElement('div'); blob.className = 'blob'; const size = Math.random() * 200 + 100; blob.style.width = `${size}px`; blob.style.height = `${size}px`; const hue = Math.floor(Math.random() * 60) + 200; blob.style.backgroundColor = `hsla(${hue}, 80%, 60%, 0.5)`; const x = Math.random() * width; const y = Math.random() * height; blob.style.left = `${x}px`; blob.style.top = `${y}px`; canvas.appendChild(blob); blobs.push({ element: blob, x: x, y: y, size: size, vx: Math.random() * 4 - 2, vy: Math.random() * 4 - 2, targetX: x, targetY: y }); } // Mouse position let mouseX = width / 2; let mouseY = height / 2; document.addEventListener('mousemove', (e) => { mouseX = e.clientX; mouseY = e.clientY; }); // Animation loop function animate() { const centerX = width / 2; const centerY = height / 2; blobs.forEach(blob => { // Attract to center but repel from mouse const dx = centerX - blob.x; const dy = centerY - blob.y; const distanceToCenter = Math.sqrt(dx * dx + dy * dy); const mx = mouseX - blob.x; const my = mouseY - blob.y; const distanceToMouse = Math.sqrt(mx * mx + my * my); // Attraction force const centerForce = 0.0002 * distanceToCenter; blob.targetX = blob.x + dx * centerForce; blob.targetY = blob.y + dy * centerForce; // Repulsion from mouse if (distanceToMouse < 300) { const repulsionForce = 300 / (distanceToMouse + 1); blob.targetX -= (mx / distanceToMouse) * repulsionForce; blob.targetY -= (my / distanceToMouse) * repulsionForce; } // Blob movement blob.x += (blob.vx + (blob.targetX - blob.x) * 0.1); blob.y += (blob.vy + (blob.targetY - blob.y) * 0.1); blob.vx *= 0.98; blob.vy *= 0.98; // Bounce off walls if (blob.x < 0 || blob.x > width) blob.vx *= -1; if (blob.y < 0 || blob.y > height) blob.vy *= -1; // Update position and size blob.element.style.left = `${blob.x - blob.size/2}px`; blob.element.style.top = `${blob.y - blob.size/2}px`; // Pulsing effect const pulse = Math.sin(Date.now() * 0.002 + blob.x * 0.01) * 0.1 + 0.9; blob.element.style.transform = `scale(${pulse})`; // Hover effect if (distanceToMouse < 100) { const dissolve = distanceToMouse / 100; blob.element.style.opacity = dissolve * 0.7; blob.element.style.filter = `blur(${10 - dissolve * 5}px)`; } else { blob.element.style.opacity = 0.7; blob.element.style.filter = 'blur(5px)'; } }); // Adjust audio based on blob density/opacity const avgOpacity = blobs.reduce((sum, blob) => sum + parseFloat(blob.element.style.opacity), 0) / blobs.length; audio.volume = 0.1 + avgOpacity * 0.3; audio.playbackRate = 0.8 + avgOpacity * 0.4; requestAnimationFrame(animate); } animate(); // Handle window resize window.addEventListener('resize', () => { width = window.innerWidth; height = window.innerHeight; }); }); </script> </body> </html> ``` This implementation creates: - A canvas of floating, pulsating liquid-like blobs that merge and interact - Semi-transparent divs that dissolve when the cursor approaches - A dynamic audio backdrop that responds to the visual density - A mouse-reactive environment where the liquid forms ripple away from cursor movement - Continuous, organic morphing between shapes - No text or navigation - just pure liquid interaction The experience will be different every time as the elements follow natural physics while also responding to user input, creating an emergent, organic behavior.
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Liquid Shape</title> <style> body { margin: 0; background: linear-gradient(to bottom, #3498db, #2ecc71); background-size: 100% 1000px; background-position: 0% 0%; } .shape { position: absolute; width: 100px; height: 100px; background-color: rgba(255, 255, 255, 0.5); border-radius: 50%; transition: all 0.5s; } </style> </head> <body> <canvas id="canvas"></canvas> <script> let canvas = document.getElementById('canvas'); canvas.width = window.innerWidth; canvas.height = window.innerHeight; let ctx = canvas.getContext('2d'); let shapes = []; let numShapes = 100; let sound = new AudioContext(); let oscillator = sound.createOscillator(); let gain = sound.createGain(); oscillator.type = 'sine'; oscillator.connect(gain); gain.connect(sound.destination); oscillator.start(); for (let i = 0; i < numShapes; i++) { shapes.push({ x: Math.random() * canvas.width, y: Math.random() * canvas.height, vx: Math.random() * 2 - 1, vy: Math.random() * 2 - 1, radius: Math.random() * 50 + 10, alpha: Math.random() * 0.5 }); } function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = 'rgba(255, 255, 255, 0.1)'; ctx.fillRect(0, 0, canvas.width, canvas.height); for (let i = 0; i < numShapes; i++) { ctx.beginPath(); ctx.arc(shapes[i].x, shapes[i].y, shapes[i].radius, 0, 2 * Math.PI); ctx.fillStyle = `rgba(255, 255, 255, ${shapes[i].alpha})`; ctx.fill(); shapes[i].x += shapes[i].vx; shapes[i].y += shapes[i].vy; if (shapes[i].x + shapes[i].radius > canvas.width || shapes[i].x - shapes[i].radius < 0) { shapes[i].vx *= -1; } if (shapes[i].y + shapes[i].radius > canvas.height || shapes[i].y - shapes[i].radius < 0) { shapes[i].vy *= -1; } if (Math.random() < 0.1) { shapes[i].alpha += Math.random() * 0.1 - 0.05; if (shapes[i].alpha < 0) shapes[i].alpha = 0; if (shapes[i].alpha > 0.5) shapes[i].alpha = 0.5; } } let density = 0; for (let i = 0; i < numShapes; i++) { density += shapes[i].alpha; } density /= numShapes; oscillator.frequency.value = density * 1000 + 20; requestAnimationFrame(draw); } draw(); canvas.addEventListener('mousemove', (e) => { let x = e.clientX; let y = e.clientY; for (let i = 0; i < numShapes; i++) { let dx = x - shapes[i].x; let dy = y - shapes[i].y; let dist = Math.sqrt(dx * dx + dy * dy); if (dist < shapes[i].radius) { shapes[i].vx += Math.random() * 2 - 1; shapes[i].vy += Math.random() * 2 - 1; shapes[i].alpha += Math.random() * 0.1 - 0.05; if (shapes[i].alpha < 0) shapes[i].alpha = 0; if (shapes[i].alpha > 0.5) shapes[i].alpha = 0.5; } } }); function updateBackground() { let gradient = ctx.createLinearGradient(0, 0, 0, canvas.height); gradient.addColorStop(0, `hsl(${Math.sin(Date.now() * 0.001) * 180 + 180}, 100%, 50%)`); gradient.addColorStop(1, `hsl(${Math.cos(Date.now() * 0.001) * 180 + 180}, 100%, 50%)`); document.body.style.background = `linear-gradient(to bottom, ${gradient.addColorStop(0, `hsl(${Math.sin(Date.now() * 0.001) * 180 + 180}, 100%, 50%)`)}, ${gradient.addColorStop(1, `hsl(${Math.cos(Date.now() * 0.001) * 180 + 180}, 100%, 50%)`)} )`; requestAnimationFrame(updateBackground); } updateBackground(); </script> </body> </html> ```
```html <!DOCTYPE html> <html lang="en" > <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Liquid Pulsating Shape</title> <style> @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400&display=swap'); /* Reset */ * { margin: 0; padding: 0; box-sizing: border-box; } html, body { width: 100%; height: 100%; overflow: hidden; background: #111; font-family: 'Inter', sans-serif; cursor: none; user-select: none; background: linear-gradient(130deg, #0c0c18, #230b37); position: relative; } body { --mouse-x: 0.5; --mouse-y: 0.5; --ripple-x: 50vw; --ripple-y: 50vh; } #background-canvas { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 0; pointer-events: none; user-select: none; display: block; filter: brightness(1.2) saturate(1.25); } #container { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 35vw; height: 35vw; max-width: 600px; max-height: 600px; min-width: 300px; min-height: 300px; pointer-events: auto; filter: drop-shadow(0 0 8px rgba(255 255 255 / 0.15)); user-select: none; touch-action:none; z-index: 10; position: relative; } /* Each flicker div */ .flicker-div { position: absolute; border-radius: 50%; filter: drop-shadow(0 0 2px rgba(255 255 255 / 0.05)); mix-blend-mode: screen; transition: opacity 0.2s ease; pointer-events: auto; /* initial will be overridden by inline styles */ background-color: rgba(255 255 255 / 0.06); animation-timing-function: ease-in-out; user-select:none; } .flicker-div:hover { animation: flicker-dissolve 0.8s forwards; opacity: 0 !important; } @keyframes flicker-dissolve { 0% { opacity: 0.15; transform: scale(1); filter: blur(0); } 50% { opacity: 0.1; filter: blur(0.6px); transform: scale(1.15); } 100% { opacity: 0; filter: blur(5px); transform: scale(1.4); } } </style> </head> <body> <canvas id="background-canvas"></canvas> <div id="container" aria-hidden="true"></div> <script> (() => { /* Overview: - Background canvas draws slow undulating gradient with ripple distortion following mouse. - Container holds ~60 semi-transparent overlapping divs forming a large shapeshifting shape. - Shape edges flicker & dissolve on hover. - The shape continuously pulsates and morphs using procedural noise. - The transparency of divs affects an audio oscillator pitch, sustaining a low hum that changes based on average opacity. */ // CONFIG const DIV_COUNT = 60; const CONTAINER = document.getElementById("container"); const CANVAS = document.getElementById("background-canvas"); const ctx = CANVAS.getContext("2d"); let w = window.innerWidth; let h = window.innerHeight; CANVAS.width = w; CANVAS.height = h; // For ripple effect let rippleX = w/2; let rippleY = h/2; // Mouse utility let mouse = {x: w/2, y: h/2}; // Noise function - Simplex noise for fluid morphing // Using lightweight simplex noise implementation from https://github.com/jwagner/simplex-noise.js // Embedding the library here inline: class SimplexNoise { constructor(randomOrSeed){ let random; if(!randomOrSeed) random = Math.random; else if(typeof randomOrSeed=="function") random = randomOrSeed; else{ let seed = randomOrSeed; random = function() { seed = (seed * 9301 + 49297) % 233280; return seed / 233280; } } this.p = new Uint8Array(256); for(let i=0; i<256; i++) this.p[i] = i; for(let i=0; i<255; i++){ let r = i + ~~(random()*(256 - i)); let temp = this.p[i]; this.p[i] = this.p[r]; this.p[r] = temp; } this.perm = new Uint8Array(512); for(let i=0; i<512; i++) this.perm[i] = this.p[i & 255]; this.grad3 = new Float32Array([ 1,1,0,-1,1,0,1,-1,0,-1,-1,0, 1,0,1,-1,0,1,1,0,-1,-1,0,-1, 0,1,1,0,-1,1,0,1,-1,0,-1,-1 ]); } dot(g, x, y) { return g[0]*x + g[1]*y; } noise2D(xin, yin) { const grad3 = this.grad3; const perm = this.perm; let n0 = 0, n1 = 0, n2 = 0; const F2 = 0.5*(Math.sqrt(3)-1); const G2 = (3 - Math.sqrt(3))/6; let s = (xin+yin)*F2; let i = Math.floor(xin+s); let j = Math.floor(yin+s); let t = (i + j)*G2; let X0 = i - t; let Y0 = j - t; let x0 = xin - X0; let y0 = yin - Y0; let i1, j1; if(x0 > y0){ i1=1; j1=0; } else { i1=0; j1=1; } let x1 = x0 - i1 + G2; let y1 = y0 - j1 + G2; let x2 = x0 - 1 + 2*G2; let y2 = y0 - 1 + 2*G2; let ii = i & 255; let jj = j & 255; let gi0 = (perm[ii + perm[jj]] % 12) * 3; let gi1 = (perm[ii + i1 + perm[jj + j1]] % 12) * 3; let gi2 = (perm[ii + 1 + perm[jj + 1]] % 12) * 3; let t0 = 0.5 - x0*x0 - y0*y0; if(t0 >= 0) { t0 *= t0; n0 = t0 * t0 * this.dot(grad3.subarray(gi0, gi0+2), x0, y0); } let t1 = 0.5 - x1*x1 - y1*y1; if(t1 >= 0) { t1 *= t1; n1 = t1 * t1 * this.dot(grad3.subarray(gi1, gi1+2), x1, y1); } let t2 = 0.5 - x2*x2 - y2*y2; if(t2 >= 0){ t2 *= t2; n2 = t2 * t2 * this.dot(grad3.subarray(gi2, gi2+2), x2, y2); } return 70 * (n0 + n1 + n2); } } const noise = new SimplexNoise(); // === Setup divs === // We create DIV_COUNT divs with varying initial position, size and opacity // Their positions and sizes are influenced to overlap mostly in a circular-ish area // Each div will morph continuously on its position, size, and opacity using noise functions let divs = []; // We'll store morph parameters for each div to drive animations procedurally // Each div gets a base angle for circular distribution & base size & base opacity for(let i=0; i < DIV_COUNT; i++){ const d = document.createElement("div"); d.classList.add("flicker-div"); CONTAINER.appendChild(d); // circular base position around center percentage for container size let angle = (i / DIV_COUNT) * Math.PI * 2; // radius base: 10% to 45% of container width (in px) let baseRadius = 0.15 + 0.3*Math.random(); // size base: 8% to 15% of container width let baseSizeRatio = 0.08 + 0.07*Math.random(); // base opacity: let baseOpacity = 0.04 + 0.04 * Math.random(); divs.push({ el: d, angle, baseRadius, baseSizeRatio, baseOpacity, // morph speed random speed: 0.1 + 0.25 * Math.random(), // flicker dissolve state dissolving: false, }); } // === Animation loop for divs morphing === function updateDivs(t) { // t in seconds // We'll morph each div's position, size, and opacity using noise // We'll place each div relative to container in px let containerRect = CONTAINER.getBoundingClientRect(); let containerW = containerRect.width; let containerH = containerRect.height; for(let i=0;i<divs.length;i++){ const div = divs[i]; if(div.dissolving) continue; // skip morph while dissolving, handled by CSS animation // Morphing parameters // Morph radius using noise let morphRadius = div.baseRadius + 0.04 * noise.noise2D(div.angle * 10 + t * div.speed, i * 10); // Morph angle slightly changes let morphAngle = div.angle + 0.15 * noise.noise2D(div.angle * 5 + t * div.speed * 1.5, i * 12); // Morph circle position let posX = containerW/2 + morphRadius * Math.cos(morphAngle) * containerW/2; let posY = containerH/2 + morphRadius * Math.sin(morphAngle) * containerH/2; // Morph size let morphSizeRatio = div.baseSizeRatio * (1 + 0.3 * noise.noise2D(i * 7, t * div.speed * 3)); let sizePx = containerW * morphSizeRatio; sizePx = Math.min(sizePx, containerW * 0.16); // Morph opacity with flickering base level let baseOpacity = div.baseOpacity + 0.02 * noise.noise2D(div.angle * 20 + t * 2, i * 15); baseOpacity = Math.min(Math.max(baseOpacity, 0.015), 0.12); div.el.style.width = sizePx + "px"; div.el.style.height = sizePx + "px"; div.el.style.left = (posX - sizePx/2) + "px"; div.el.style.top = (posY - sizePx/2) + "px"; div.el.style.backgroundColor = `rgba(255,255,255,${baseOpacity.toFixed(3)})`; div.el.style.opacity = "1"; // subtle blur scaling based on size div.el.style.filter = `blur(${(2 - morphSizeRatio*10).toFixed(2)}px) drop-shadow(0 0 2px rgba(255 255 255 /0.07))`; } } // === Ripple undulating background gradient === // We'll draw a large smooth gradient and displace pixels around the ripple center // Using offscreen canvas for performance if needed // To simulate multiple color layers blending with subtle movement. let bgColors = [ [16,7,32], // dark purple/blue [60,21,76], // purple [160,40,140], // pink-purple [80,15,65], // dark violet [20,15,25], // very dark near black ]; // Function to create a smooth linear gradient with animated stops // then apply a ripple distortion from mouse position // We'll draw vertical gradient with decades of subtle undulation function lerp(a,b,t){return a+(b - a)*t;} function lerpColor(c1,c2,t){ return c1.map((v,i) => lerp(v,c2[i],t)); } function colorToString(c){ return `rgb(${c[0]},${c[1]},${c[2]})`; } function drawBackground(t) { // Clear ctx.clearRect(0, 0, w, h); // Create gradient stops let grad = ctx.createLinearGradient(0, 0, w, h); // We'll create multiple color stops that slowly move positions // Positions oscillate between 0-1 with noise const count = bgColors.length; for(let i=0;i<count;i++){ // Animate stops position using noise and time let pos = (i/(count-1)) + 0.03 * noise.noise2D(i*10, t * 0.1); pos = Math.min(Math.max(pos,0),1); let c = bgColors[i]; grad.addColorStop(pos, colorToString(c)); } ctx.fillStyle = grad; ctx.fillRect(0,0,w,h); // Ripple distortion parameters const rippleRadius = Math.min(w,h)*0.35; const rippleStrength = 12; // We'll get imageData, displace pixels based on distance from ripple, but for perf we do a shader-like effect on canvas by drawing small ripple circles with alpha masks // Instead of pixel-level displacement (too expensive), // We'll overlay multiple translucent circles blending with background to simulate ripples. // Draw ripple circles let rippleCount = 7; for(let i = 0; i < rippleCount; i++){ let progress = (t*0.8 - i*0.14) % 1; if(progress < 0) progress += 1; // Calculate ripple radius expansion let rInner = rippleRadius * progress*0.8; let rOuter = rippleRadius * (progress*1.05 + 0.05); let alpha = (1 - progress)*0.09; // ripple moves with mouse plus slight movement via noise for organicness let rx = rippleX + 20 * noise.noise2D(i*20, t*0.15); let ry = rippleY + 20 * noise.noise2D(i*25, t*0.17 + 15); let radialGrad = ctx.createRadialGradient(rx, ry, rInner, rx, ry, rOuter); radialGrad.addColorStop(0, `rgba(220,180,255,${alpha})`); radialGrad.addColorStop(1, `rgba(220,180,255,0)`); ctx.fillStyle = radialGrad; ctx.beginPath(); ctx.arc(rx, ry, rOuter, 0, Math.PI * 2); ctx.fill(); } } // === Ripple distorts divs edges by CSS filter + subtle blur and small translation on mouse proximity === function rippleDivDistortion() { // We'll push rippleX, rippleY to container div so divs can use in CSS variables if needed // We'll just handle mouse proximity flicker / distortion in hover styles and morph for smoothness // Instead, subtle 3D translation on hover handled in CSS keyframes for dissolve // No additional dynamic distortion to div in JS to keep performance high } // === Flicker & dissolve handled via CSS :hover + JS triggers // We add event listeners to each div for mouseover to trigger dissolving divs.forEach((div) => { div.el.addEventListener("mouseenter", () => { if(div.dissolving) return; div.dissolving = true; div.el.style.pointerEvents = "none"; div.el.classList.add("dissolving"); // After 800ms remove div and regenerate in new position/opacity after short pause setTimeout(() => { // Reset dissolve state div.dissolving = false; div.el.classList.remove("dissolving"); div.el.style.pointerEvents = "auto"; div.el.style.opacity = "1"; }, 900); }); }); // === Audio setup === // We create low humming oscillator with oscillator pitch controlled by average transparency of divs let AudioContextClass = window.AudioContext || window.webkitAudioContext; let audioCtx; let oscillator; let gainNode; // We'll calculate average opacity of divs and map to oscillator frequency function setupAudio(){ try { audioCtx = new AudioContextClass(); oscillator = audioCtx.createOscillator(); gainNode = audioCtx.createGain(); oscillator.type = "sine"; oscillator.frequency.setValueAtTime(65.41, audioCtx.currentTime); // C2 note ~65Hz base gainNode.gain.setValueAtTime(0.02, audioCtx.currentTime); oscillator.connect(gainNode).connect(audioCtx.destination); oscillator.start(); } catch(e) { console.warn("Audio not supported or blocked:", e); } } // Map average opacity to frequency between 55Hz (A1) and 110Hz (A2) // opacity typically between 0.015 and 0.12 per div, average is scaled function updateAudioFrequency(avgOpacity) { if(!audioCtx || !oscillator) return; // map avgOpacity (0.015 to 0.12) to frequency 55 to 110 Hz let minO = 0.015; let maxO = 0.12; let normalized = Math.min(Math.max((avgOpacity - minO) / (maxO - minO), 0), 1); let freq = 55 + 55 * normalized; // smooth frequency change oscillator.frequency.exponentialRampToValueAtTime(freq, audioCtx.currentTime + 0.1); } // === Mouse Handler === window.addEventListener("mousemove", e => { mouse.x = e.clientX; mouse.y = e.clientY; rippleX = e.clientX; rippleY = e.clientY; }); window.addEventListener("touchmove", e => { if(e.touches.length > 0){ mouse.x = e.touches[0].clientX; mouse.y = e.touches[0].clientY; rippleX = e.touches[0].clientX; rippleY = e.touches[0].clientY; } }, {passive:true}); // === Resize Handler === window.addEventListener("resize", () => { w = window.innerWidth; h = window.innerHeight; CANVAS.width = w; CANVAS.height = h; }); // Start audio on first user interaction (to comply with browser autoplay policies) function enableAudioOnInteract(){ if(!audioCtx){ setupAudio(); } else if(audioCtx.state === 'suspended'){ audioCtx.resume(); } window.removeEventListener('mousedown', enableAudioOnInteract); window.removeEventListener('touchstart', enableAudioOnInteract); window.removeEventListener('keydown', enableAudioOnInteract); } window.addEventListener('mousedown', enableAudioOnInteract); window.addEventListener('touchstart', enableAudioOnInteract); window.addEventListener('keydown', enableAudioOnInteract); // === Main Loop === let lastTimestamp = 0; function animate(ts=0){ if(!lastTimestamp) lastTimestamp = ts; let dt = (ts - lastTimestamp)/1000; lastTimestamp = ts; let t = ts/1000; updateDivs(t); drawBackground(t); rippleDivDistortion(); // Calculate average opacity of all visible divs let totalOpacity = 0; let visibleCount = 0; divs.forEach(div => { if(!div.dissolving){ let style = window.getComputedStyle(div.el); let op = parseFloat(style.backgroundColor.match(/rgba\(.+,.+,.+,(.+)\)/)?.[1]) || div.baseOpacity; totalOpacity += op; visibleCount++; } }); let avgOpacity = (visibleCount>0) ? (totalOpacity/visibleCount) : 0; updateAudioFrequency(avgOpacity); requestAnimationFrame(animate); } requestAnimationFrame(animate); })(); </script> </body> </html> ```
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Liquid Shape</title> <style> html, body { margin: 0; overflow: hidden; height: 100%; width: 100%; background: linear-gradient(120deg, #00f, #f00); background-size: 400% 400%; animation: backgroundRipple 10s ease-in-out infinite; } @keyframes backgroundRipple { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } } #shape { position: absolute; top: 50%; left: 50%; width: 200px; height: 200px; background: rgba(0, 0, 255, 0.5); border-radius: 50%; transform: translate(-50%, -50%) scale(1); animation: pulse 4s ease-in-out infinite; mix-blend-mode: screen; } @keyframes pulse { 0%, 100% { transform: translate(-50%, -50%) scale(1) rotate(0deg); border-radius: 50%; } 50% { transform: translate(-50%, -50%) scale(1.2) rotate(180deg); border-radius: 50% 0% 50% 0%; } } .edge { position: absolute; width: 10px; height: 10px; background: rgba(0, 0, 255, 0.2); border-radius: 50%; pointer-events: none; animation: edgeAnimation 2s linear infinite; opacity: 0.2; } @keyframes edgeAnimation { 0% { transform: scale(1); } 50% { transform: scale(1.5); } 100% { transform: scale(1); } } .hovered { animation: flicker 0.3s forwards; } @keyframes flicker { 0% { opacity: 1; transform: scale(1); } 50% { opacity: 0.3; transform: scale(0.5); } 100% { opacity: 1; transform: scale(1); } } </style> </head> <body> <div id="shape"></div> <script> const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); const osc = audioCtx.createOscillator(); const gain = audioCtx.createGain(); osc.type = 'sine'; osc.frequency.value = 200; // Base frequency osc.connect(gain); gain.connect(audioCtx.destination); osc.start(); const edges = []; const edgeCount = 20; for (let i = 0; i < edgeCount; i++) { const edge = document.createElement('div'); edge.classList.add('edge'); edge.style.left = Math.random() * window.innerWidth + 'px'; edge.style.top = Math.random() * window.innerHeight + 'px'; edge.style.width = Math.random() * 20 + 10 + 'px'; edge.style.height = edge.style.width; edge.style.opacity = 0.2 + Math.random() * 0.3; document.body.appendChild(edge); edges.push(edge); } function updateFrequency() { let total = 0; edges.forEach(edge => { total += parseFloat(edge.style.opacity); }); const average = total / edges.length; osc.frequency.value = 200 - (average * 100); // Adjust frequency based on average opacity } edges.forEach(edge => { edge.addEventListener('mouseover', () => { edge.classList.add('hovered'); updateFrequency(); }); edge.addEventListener('mouseout', () => { edge.classList.remove('hovered'); updateFrequency(); }); }); document.addEventListener('mousemove', (e) => { const x = e.clientX / window.innerWidth; const y = e.clientY / window.innerHeight; const offset = 50 + (x * 100) + (y * 100); document.body.style.backgroundPosition = `${offset}% ${offset}%`; }); // Initial frequency update updateFrequency(); </script> </body> </html> ```