
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>
```