Water effects can transform a simple design into a visually stunning masterpiece. From ripples to reflections, these effects add a dynamic and engaging element to any project.
In this article, we will explore 10 captivating water effect examples that can elevate your design game. Whether you're a seasoned designer or just starting out, these examples will inspire and guide you.
CODE1
Here's the code:
CODETEXT1
CODE2
Here's the code:
CODETEXT2
CODE3
Here's the code:
CODETEXT3
CODE4
Here's the code:
CODETEXT4
CODE5
Here's the code:
CODETEXT5
Subframe's drag-and-drop interface and intuitive, responsive canvas make it easy to create pixel-perfect water effects. Loved by designers and developers alike, Subframe ensures stunning results every time.
Start for free and elevate your design game today!
CODE6
Here's the code:
CODETEXT6
CODE7
Here's the code:
CODETEXT7
CODE8
Here's the code:
CODETEXT8
CODE9
Here's the code:
CODETEXT9
CODE10
Here's the code:
CODETEXT10
Ready to elevate your design game? With Subframe, you can create pixel-perfect UIs, including stunning water effects, in minutes. Our drag-and-drop editor and beautifully crafted components make designing efficient and effortless.
Don't waitβstart for free and begin creating immediately. Experience the power of Subframe today!
<html> <head> <style> :root { --primary-blue: #1e88e5; --secondary-blue: #0d47a1; --tertiary-blue: #bbdefb; --light-blue: #e3f2fd; --white: #ffffff; } * { box-sizing: border-box; margin: 0; padding: 0; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { display: flex; justify-content: center; align-items: center; height: 100vh; width: 100%; background-color: var(--light-blue); overflow: hidden; } .container { width: 100%; max-width: 700px; height: 100%; max-height: 700px; display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 2rem; padding: 2rem; } .title { color: var(--secondary-blue); text-align: center; font-weight: 600; margin-bottom: 1rem; opacity: 0; transform: translateY(-20px); animation: fade-in 0.8s ease forwards; } .subtitle { color: var(--primary-blue); text-align: center; font-weight: 400; margin-bottom: 2rem; max-width: 600px; opacity: 0; transform: translateY(-20px); animation: fade-in 0.8s ease 0.2s forwards; } .buttons-container { display: flex; flex-wrap: wrap; justify-content: center; gap: 2rem; margin-top: 1rem; } .button-wrapper { position: relative; overflow: hidden; border-radius: 50px; opacity: 0; transform: translateY(20px); animation: fade-in 0.8s ease 0.4s forwards; } .water-button { position: relative; padding: 16px 36px; background-color: var(--primary-blue); color: var(--white); border: none; border-radius: 50px; cursor: pointer; font-size: 16px; font-weight: 600; letter-spacing: 1px; overflow: hidden; transition: all 0.3s ease; z-index: 1; text-transform: uppercase; box-shadow: 0 4px 20px rgba(30, 136, 229, 0.3); } .water-button:hover { box-shadow: 0 8px 25px rgba(30, 136, 229, 0.5); transform: translateY(-2px); } .ripple { position: absolute; border-radius: 50%; background-color: rgba(255, 255, 255, 0.7); transform: scale(0); animation: ripple 0.8s linear; pointer-events: none; } .water-drop { position: absolute; width: 15px; height: 15px; background-color: rgba(255, 255, 255, 0.7); border-radius: 50% 50% 50% 0; transform: rotate(-45deg); animation: drop 0.8s ease-out forwards; pointer-events: none; } .droplet-container { position: absolute; top: 0; left: 0; width: 100%; height: 100%; overflow: hidden; pointer-events: none; } .water-circle { position: absolute; border-radius: 50%; background: rgba(255, 255, 255, 0.3); transform: scale(0); animation: circle-expand 0.8s ease-out forwards; } .info-panel { margin-top: 2rem; background-color: var(--white); border-radius: 16px; padding: 1.5rem; box-shadow: 0 8px 30px rgba(13, 71, 161, 0.1); max-width: 500px; opacity: 0; transform: translateY(20px); animation: fade-in 0.8s ease 0.6s forwards; } .info-title { color: var(--secondary-blue); font-size: 1.2rem; margin-bottom: 0.8rem; } .info-text { color: #444; font-size: 0.95rem; line-height: 1.6; } .interaction-hint { display: flex; align-items: center; margin-top: 1rem; padding: 0.8rem; background-color: var(--tertiary-blue); border-radius: 8px; font-size: 0.9rem; color: var(--secondary-blue); } .hint-icon { margin-right: 0.8rem; font-size: 1.2rem; } @keyframes ripple { to { transform: scale(4); opacity: 0; } } @keyframes drop { 0% { transform: rotate(-45deg) translateY(0); opacity: 1; } 70% { opacity: 1; } 100% { transform: rotate(-45deg) translateY(200px); opacity: 0; } } @keyframes circle-expand { to { transform: scale(3); opacity: 0; } } @keyframes fade-in { to { opacity: 1; transform: translateY(0); } } @media (max-width: 500px) { .buttons-container { flex-direction: column; gap: 1.5rem; } .title { font-size: 1.5rem; } .subtitle { font-size: 1rem; } .info-panel { padding: 1rem; } .water-button { padding: 14px 28px; font-size: 14px; } } </style> </head> <body> <div class="container"> <h1 class="title">Fluid Interaction Design</h1> <p class="subtitle">Experience the natural flow of water in these interactive buttons. Hover over or click to see cascading ripples and droplet effects.</p> <div class="buttons-container"> <div class="button-wrapper"> <button class="water-button" id="splash-button">Splash Effect</button> <div class="droplet-container" id="splash-container"></div> </div> <div class="button-wrapper"> <button class="water-button" id="ripple-button">Ripple Effect</button> <div class="droplet-container" id="ripple-container"></div> </div> <div class="button-wrapper"> <button class="water-button" id="droplet-button">Droplet Effect</button> <div class="droplet-container" id="droplet-container"></div> </div> </div> <div class="info-panel"> <h3 class="info-title">Water-Inspired Microinteractions</h3> <p class="info-text">These buttons incorporate fluid dynamics principles to create natural, responsive feedback. Each button demonstrates a different water behavior - from gentle ripples to cascading droplets - providing tactile visual feedback that enhances the user experience.</p> <div class="interaction-hint"> <span class="hint-icon">π‘</span> <span>Try both hovering and clicking each button to experience different water effects.</span> </div> </div> </div> <script> document.addEventListener('DOMContentLoaded', () => { // Set up event listeners for all buttons setupSplashButton(); setupRippleButton(); setupDropletButton(); // Create ripple effect on click for all buttons document.querySelectorAll('.water-button').forEach(button => { button.addEventListener('click', createRippleEffect); }); }); function setupSplashButton() { const button = document.getElementById('splash-button'); const container = document.getElementById('splash-container'); button.addEventListener('mouseenter', () => { // Create a water circle in the center of the button const circle = document.createElement('div'); circle.classList.add('water-circle'); const buttonRect = button.getBoundingClientRect(); const size = Math.max(buttonRect.width, buttonRect.height) * 0.8; circle.style.width = `${size}px`; circle.style.height = `${size}px`; circle.style.left = `${buttonRect.width / 2 - size / 2}px`; circle.style.top = `${buttonRect.height / 2 - size / 2}px`; container.appendChild(circle); // Remove the circle after animation completes setTimeout(() => { circle.remove(); }, 800); }); button.addEventListener('click', () => { for (let i = 0; i < 8; i++) { setTimeout(() => { createSplashDroplet(container); }, i * 50); } }); } function setupRippleButton() { const button = document.getElementById('ripple-button'); const container = document.getElementById('ripple-container'); button.addEventListener('mouseenter', (e) => { const buttonRect = button.getBoundingClientRect(); const x = e.clientX - buttonRect.left; // Create multiple ripples from the entry point for (let i = 0; i < 3; i++) { setTimeout(() => { const ripple = document.createElement('div'); ripple.classList.add('ripple'); ripple.style.left = `${x}px`; ripple.style.top = `${buttonRect.height / 2}px`; container.appendChild(ripple); setTimeout(() => { ripple.remove(); }, 800); }, i * 150); } }); button.addEventListener('mousemove', (e) => { if (Math.random() > 0.92) { // Occasionally add ripple on move const buttonRect = button.getBoundingClientRect(); const x = e.clientX - buttonRect.left; const y = e.clientY - buttonRect.top; const ripple = document.createElement('div'); ripple.classList.add('ripple'); ripple.style.width = '10px'; ripple.style.height = '10px'; ripple.style.left = `${x}px`; ripple.style.top = `${y}px`; container.appendChild(ripple); setTimeout(() => { ripple.remove(); }, 800); } }); } function setupDropletButton() { const button = document.getElementById('droplet-button'); const container = document.getElementById('droplet-container'); button.addEventListener('mouseenter', () => { // Create water droplets falling from top for (let i = 0; i < 5; i++) { setTimeout(() => { const droplet = document.createElement('div'); droplet.classList.add('water-drop'); const buttonRect = button.getBoundingClientRect(); const randomX = Math.random() * buttonRect.width; droplet.style.left = `${randomX}px`; droplet.style.top = '0px'; container.appendChild(droplet); setTimeout(() => { droplet.remove(); }, 800); }, i * 120); } }); button.addEventListener('click', () => { // Create a burst of droplets in all directions for (let i = 0; i < 12; i++) { setTimeout(() => { const buttonRect = button.getBoundingClientRect(); const centerX = buttonRect.width / 2; const centerY = buttonRect.height / 2; const droplet = document.createElement('div'); droplet.classList.add('water-drop'); droplet.style.left = `${centerX}px`; droplet.style.top = `${centerY}px`; const angle = i * 30 * (Math.PI / 180); const distance = 40; const xOffset = Math.cos(angle) * distance; const yOffset = Math.sin(angle) * distance; droplet.style.transform = `translate(${xOffset}px, ${yOffset}px) rotate(${i * 30}deg)`; container.appendChild(droplet); // Custom animation for burst droplets droplet.animate([ { transform: `translate(0, 0) rotate(${i * 30}deg)`, opacity: 1 }, { transform: `translate(${xOffset * 3}px, ${yOffset * 3}px) rotate(${i * 30}deg)`, opacity: 0 } ], { duration: 800, easing: 'cubic-bezier(0.2, 0.8, 0.2, 1)' }); setTimeout(() => { droplet.remove(); }, 800); }, i * 30); } }); } function createRippleEffect(e) { const button = e.currentTarget; const ripple = document.createElement('div'); ripple.classList.add('ripple'); const buttonRect = button.getBoundingClientRect(); const x = e.clientX - buttonRect.left; const y = e.clientY - buttonRect.top; ripple.style.left = `${x}px`; ripple.style.top = `${y}px`; button.appendChild(ripple); setTimeout(() => { ripple.remove(); }, 800); } function createSplashDroplet(container) { const droplet = document.createElement('div'); droplet.classList.add('water-drop'); const containerRect = container.getBoundingClientRect(); const centerX = containerRect.width / 2; const centerY = containerRect.height / 2; droplet.style.left = `${centerX}px`; droplet.style.top = `${centerY}px`; const angle = Math.random() * Math.PI * 2; const distance = 20 + Math.random() * 30; const xOffset = Math.cos(angle) * distance; const yOffset = Math.sin(angle) * distance; droplet.style.transform = `translate(${xOffset}px, ${yOffset}px) rotate(${Math.random() * 360}deg)`; container.appendChild(droplet); // Custom animation for splash droplets droplet.animate([ { transform: `translate(0, 0) rotate(0deg)`, opacity: 1 }, { transform: `translate(${xOffset * 2}px, ${yOffset * 2}px) rotate(${Math.random() * 360}deg)`, opacity: 0 } ], { duration: 600, easing: 'cubic-bezier(0.1, 0.9, 0.2, 1)' }); setTimeout(() => { droplet.remove(); }, 600); } </script> </body> </html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Flowing Waters</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Montserrat', sans-serif; } body { height: 100vh; width: 100%; overflow: hidden; background: linear-gradient(135deg, #062141 0%, #0a4675 50%, #1a6ea8 100%); color: #fff; position: relative; } canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; } .content { position: relative; z-index: 2; height: 100%; width: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 30px; text-align: center; } h1 { font-size: 3rem; margin-bottom: 1.2rem; line-height: 1.1; font-weight: 700; text-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); background: linear-gradient(90deg, #ffffff, #b8e0ff); -webkit-background-clip: text; background-clip: text; color: transparent; transition: transform 0.5s ease; } p { max-width: 600px; margin-bottom: 2rem; font-size: 1.1rem; font-weight: 300; line-height: 1.6; letter-spacing: 0.02em; opacity: 0.9; } .cta-button { background: rgba(255, 255, 255, 0.15); backdrop-filter: blur(8px); border: 1px solid rgba(255, 255, 255, 0.3); padding: 14px 30px; border-radius: 30px; color: white; font-size: 1rem; font-weight: 500; cursor: pointer; transition: all 0.3s ease; position: relative; overflow: hidden; text-decoration: none; display: inline-block; margin-top: 1rem; } .cta-button::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: all 0.6s ease; } .cta-button:hover { background: rgba(255, 255, 255, 0.25); transform: translateY(-3px); box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1); } .cta-button:hover::before { left: 100%; } .floating-elements { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 1; overflow: hidden; } .bubble { position: absolute; background: radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0.05)); border-radius: 50%; border: 1px solid rgba(255, 255, 255, 0.1); animation: float 15s ease-in-out infinite; opacity: 0; } @keyframes float { 0% { transform: translateY(100%) translateX(0) scale(0.3); opacity: 0; } 10% { opacity: 0.8; } 100% { transform: translateY(-100%) translateX(20px) scale(1); opacity: 0; } } .watermark { position: absolute; bottom: 20px; right: 20px; font-size: 0.85rem; opacity: 0.5; z-index: 3; } .depth-indicator { position: absolute; right: 30px; top: 50%; transform: translateY(-50%); height: 200px; width: 30px; background: rgba(255, 255, 255, 0.1); border-radius: 15px; overflow: hidden; z-index: 3; backdrop-filter: blur(5px); border: 1px solid rgba(255, 255, 255, 0.2); } .depth-fill { position: absolute; bottom: 0; width: 100%; height: 60%; background: linear-gradient(to top, #a2d3ff, #5b9add); border-radius: 0 0 15px 15px; transition: height 2s ease; } .depth-marker { position: absolute; width: 100%; height: 1px; background: rgba(255, 255, 255, 0.5); left: 0; transform-origin: left; } .depth-marker:nth-child(1) { top: 25%; } .depth-marker:nth-child(2) { top: 50%; } .depth-marker:nth-child(3) { top: 75%; } .depth-label { position: absolute; right: 40px; font-size: 0.7rem; color: rgba(255, 255, 255, 0.7); } .depth-label:nth-child(4) { top: 25%; transform: translateY(-50%); } .depth-label:nth-child(5) { top: 50%; transform: translateY(-50%); } .depth-label:nth-child(6) { top: 75%; transform: translateY(-50%); } @media (max-width: 768px) { h1 { font-size: 2.2rem; } p { font-size: 1rem; } .depth-indicator { display: none; } } @media (max-width: 480px) { h1 { font-size: 1.8rem; } p { font-size: 0.9rem; } .cta-button { padding: 12px 24px; font-size: 0.9rem; } } /* Interactive ripple effect */ .ripple-container { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 2; pointer-events: none; overflow: hidden; } .ripple { position: absolute; border-radius: 50%; background: radial-gradient(circle, rgba(255, 255, 255, 0.4) 0%, rgba(255, 255, 255, 0) 70%); transform: scale(0); animation: ripple-animation 2s ease-out forwards; } @keyframes ripple-animation { to { transform: scale(3); opacity: 0; } } </style> </head> <body> <canvas id="waterCanvas"></canvas> <div class="floating-elements" id="bubbleContainer"></div> <div class="ripple-container" id="rippleContainer"></div> <div class="content"> <h1>Dive into Fluid Experiences</h1> <p>The ocean covers 71% of Earth's surface yet remains largely unexplored. Our cutting-edge water simulation technology brings the serene beauty of underwater currents to digital interfaces, creating immersive environments that flow with your content.</p> <a href="#" class="cta-button" id="rippleButton">Explore the Depths</a> </div> <div class="depth-indicator"> <div class="depth-marker"></div> <div class="depth-marker"></div> <div class="depth-marker"></div> <div class="depth-label">100m</div> <div class="depth-label">200m</div> <div class="depth-label">300m</div> <div class="depth-fill"></div> </div> <div class="watermark">Oceanic Interfaces β’ v2.4</div> <script> // Water Canvas Animation document.addEventListener('DOMContentLoaded', function() { const canvas = document.getElementById('waterCanvas'); const ctx = canvas.getContext('2d'); let width, height; // Resize handling function resize() { width = canvas.width = window.innerWidth; height = canvas.height = window.innerHeight; } window.addEventListener('resize', resize); resize(); // Wave parameters const waveCount = 3; const waves = []; // Create waves with different properties for (let i = 0; i < waveCount; i++) { waves.push({ amplitude: 15 + Math.random() * 20, period: 100 + Math.random() * 200, phase: Math.random() * Math.PI * 2, speed: 0.03 + Math.random() * 0.05 }); } // Animation setup let animationFrameId; let time = 0; function animate() { ctx.clearRect(0, 0, width, height); // Draw each layer from bottom to top with transparency drawWaveLayer(time, 0.9, 0, 2.5, 'rgba(10, 50, 90, 0.75)'); drawWaveLayer(time, 0.7, 0.1, 2, 'rgba(20, 80, 130, 0.6)'); drawWaveLayer(time, 0.5, 0.2, 1.5, 'rgba(40, 120, 170, 0.5)'); drawWaveLayer(time, 0.3, 0.3, 1, 'rgba(80, 160, 210, 0.3)'); time += 0.01; animationFrameId = requestAnimationFrame(animate); } function drawWaveLayer(time, yPos, phaseOffset, amplitude, color) { const y = height * yPos; ctx.fillStyle = color; ctx.beginPath(); ctx.moveTo(0, height); // Start wave path ctx.lineTo(0, y); // Draw the wave across the width for (let x = 0; x < width; x++) { let waveY = 0; // Combine multiple waves for complexity for (const wave of waves) { waveY += Math.sin(x / wave.period + wave.phase + time * wave.speed + phaseOffset) * wave.amplitude * amplitude; } ctx.lineTo(x, y + waveY); } // Complete the path ctx.lineTo(width, y); ctx.lineTo(width, height); ctx.closePath(); ctx.fill(); } // Start animation animate(); // Cleanup on page unload return () => { cancelAnimationFrame(animationFrameId); window.removeEventListener('resize', resize); }; }); // Bubble Animation document.addEventListener('DOMContentLoaded', function() { const bubbleContainer = document.getElementById('bubbleContainer'); const maxBubbles = 15; function createBubble() { const bubble = document.createElement('div'); bubble.classList.add('bubble'); // Random properties const size = 20 + Math.random() * 60; const xPos = Math.random() * 100; const duration = 15 + Math.random() * 10; const delay = Math.random() * 5; // Apply styles bubble.style.width = `${size}px`; bubble.style.height = `${size}px`; bubble.style.left = `${xPos}%`; bubble.style.bottom = `-${size}px`; bubble.style.animationDuration = `${duration}s`; bubble.style.animationDelay = `${delay}s`; bubbleContainer.appendChild(bubble); // Remove the bubble after animation completes setTimeout(() => { bubble.remove(); createBubble(); }, (duration + delay) * 1000); } // Create initial bubbles for (let i = 0; i < maxBubbles; i++) { createBubble(); } }); // Depth indicator animation document.addEventListener('DOMContentLoaded', function() { const depthFill = document.querySelector('.depth-fill'); const heights = [30, 45, 60, 75, 90]; let currentIndex = 0; function animateDepth() { currentIndex = (currentIndex + 1) % heights.length; depthFill.style.height = `${heights[currentIndex]}%`; setTimeout(animateDepth, 8000); } setTimeout(animateDepth, 3000); }); // Interactive ripple effect on click document.addEventListener('DOMContentLoaded', function() { const rippleContainer = document.getElementById('rippleContainer'); const rippleButton = document.getElementById('rippleButton'); const content = document.querySelector('.content'); function createRipple(x, y) { const ripple = document.createElement('div'); ripple.classList.add('ripple'); // Set size and position const size = Math.max(window.innerWidth, window.innerHeight) * 0.3; ripple.style.width = `${size}px`; ripple.style.height = `${size}px`; ripple.style.left = `${x - size/2}px`; ripple.style.top = `${y - size/2}px`; rippleContainer.appendChild(ripple); // Remove after animation setTimeout(() => { ripple.remove(); }, 2000); } // Add ripple on document click document.addEventListener('click', function(e) { if (e.target !== rippleButton) { createRipple(e.clientX, e.clientY); } }); // Special effect for button click rippleButton.addEventListener('click', function(e) { e.preventDefault(); // Create multiple ripples for button const rect = rippleButton.getBoundingClientRect(); const centerX = rect.left + rect.width / 2; const centerY = rect.top + rect.height / 2; for (let i = 0; i < 3; i++) { setTimeout(() => { createRipple(centerX, centerY); }, i * 300); } // Highlight title with subtle animation const title = document.querySelector('h1'); title.style.transform = 'scale(1.05)'; setTimeout(() => { title.style.transform = 'scale(1)'; }, 500); }); }); </script> </body> </html>
<html> <head> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; background-color: #0f172a; font-family: 'Inter', sans-serif; overflow: hidden; } .container { position: relative; width: 700px; height: 700px; display: flex; flex-direction: column; justify-content: center; align-items: center; } .loading-text { color: rgba(255, 255, 255, 0.9); font-size: 20px; letter-spacing: 0.5px; position: absolute; bottom: 24%; text-align: center; opacity: 0; animation: fade-in 1s ease 0.5s forwards; z-index: 100; } .loading-text h2 { font-weight: 700; font-size: 28px; margin-bottom: 1rem; color: #60a5fa; } .loading-text p { font-weight: 400; line-height: 1.6; color: rgba(255, 255, 255, 0.7); max-width: 500px; } .loading-percentage { position: absolute; font-size: 50px; font-weight: 700; color: rgba(255, 255, 255, 0.9); top: 32%; opacity: 0; animation: fade-in 1s ease 0.8s forwards; z-index: 100; text-shadow: 0 0 20px rgba(96, 165, 250, 0.3); } .water-container { position: relative; width: 220px; height: 220px; border-radius: 50%; overflow: hidden; box-shadow: 0 0 50px rgba(96, 165, 250, 0.2); transform: scale(0.9); animation: pulse 4s ease-in-out infinite; } .water { position: absolute; bottom: 0; left: 0; width: 100%; height: 0%; background: linear-gradient(0deg, #3b82f6 0%, #60a5fa 80%, #93c5fd 100%); transition: height 0.5s ease; border-radius: 0 0 50% 50%; animation: fillWater 5s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; } .droplet-container { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; pointer-events: none; } .droplet { position: absolute; width: 20px; height: 20px; background-color: #60a5fa; border-radius: 50%; opacity: 0; transform: scale(0); box-shadow: 0 0 10px rgba(96, 165, 250, 0.5); } .ripple { position: absolute; width: 10px; height: 10px; background-color: rgba(255, 255, 255, 0.4); border-radius: 50%; transform: scale(0); opacity: 0; } .wave { position: absolute; bottom: 0; left: 0; width: 200%; height: 100%; background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320"><path fill="white" fill-opacity="0.3" d="M0,192L48,186.7C96,181,192,171,288,181.3C384,192,480,224,576,234.7C672,245,768,235,864,224C960,213,1056,203,1152,197.3C1248,192,1344,192,1392,192L1440,192L1440,320L1392,320C1344,320,1248,320,1152,320C1056,320,960,320,864,320C768,320,672,320,576,320C480,320,384,320,288,320C192,320,96,320,48,320L0,320Z"></path></svg>'); background-repeat: repeat-x; animation: waveAnimation 10s linear infinite; } .wave:nth-child(2) { bottom: 5px; opacity: 0.6; animation: waveAnimation 7s linear infinite; } @keyframes waveAnimation { 0% { transform: translateX(0); } 100% { transform: translateX(-50%); } } @keyframes fillWater { 0% { height: 0%; } 20% { height: 15%; } 40% { height: 40%; } 60% { height: 60%; } 80% { height: 75%; } 100% { height: 85%; } } @keyframes dropletFall { 0% { transform: translate(0, -100px) scale(0.8); opacity: 0.8; } 70% { transform: translate(0, 0) scale(1); opacity: 1; } 100% { transform: translate(0, 0) scale(0); opacity: 0; } } @keyframes rippleEffect { 0% { transform: scale(0); opacity: 1; } 100% { transform: scale(15); opacity: 0; } } @keyframes pulse { 0% { box-shadow: 0 0 30px rgba(96, 165, 250, 0.2); } 50% { box-shadow: 0 0 50px rgba(96, 165, 250, 0.4); } 100% { box-shadow: 0 0 30px rgba(96, 165, 250, 0.2); } } @keyframes fade-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } .particles { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; } .particle { position: absolute; background-color: rgba(96, 165, 250, 0.6); border-radius: 50%; pointer-events: none; } </style> </head> <body> <div class="container"> <div class="loading-percentage">0%</div> <div class="water-container"> <div class="water"> <div class="wave"></div> <div class="wave"></div> </div> </div> <div class="droplet-container"></div> <div class="particles"></div> <div class="loading-text"> <h2>Fluid Rendering Engine</h2> <p>Preparing high-resolution water dynamics and optimizing surface tension calculations for your experience.</p> </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { const loadingPercentage = document.querySelector('.loading-percentage'); const waterContainer = document.querySelector('.water-container'); const dropletContainer = document.querySelector('.droplet-container'); const particlesContainer = document.querySelector('.particles'); let currentPercentage = 0; const targetPercentage = 100; let loadingInterval; // Create random water particles in the background function createParticles() { for (let i = 0; i < 50; i++) { const particle = document.createElement('div'); particle.classList.add('particle'); // Random size between 2px and 6px const size = 2 + Math.random() * 4; particle.style.width = `${size}px`; particle.style.height = `${size}px`; // Random position const x = Math.random() * 100; const y = Math.random() * 100; particle.style.left = `${x}%`; particle.style.top = `${y}%`; // Random opacity particle.style.opacity = 0.1 + Math.random() * 0.3; // Animation particle.style.animation = `pulse ${3 + Math.random() * 4}s ease-in-out infinite alternate`; particlesContainer.appendChild(particle); } } // Create a water droplet function createDroplet() { if (currentPercentage >= targetPercentage) return; const droplet = document.createElement('div'); droplet.classList.add('droplet'); // Random position at the top const x = -50 + Math.random() * 100; droplet.style.left = `${x}%`; droplet.style.top = '-50px'; // Random size const size = 15 + Math.random() * 15; droplet.style.width = `${size}px`; droplet.style.height = `${size}px`; // Animation droplet.style.animation = `dropletFall ${0.5 + Math.random() * 0.5}s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards`; dropletContainer.appendChild(droplet); // Create ripple effect when droplet hits water setTimeout(() => { createRipple(x); droplet.remove(); }, 500 + Math.random() * 500); // Remove droplet after animation setTimeout(() => { if (droplet.parentNode) { droplet.remove(); } }, 1500); } // Create ripple effect function createRipple(x) { const ripple = document.createElement('div'); ripple.classList.add('ripple'); // Position at the water surface const waterHeight = document.querySelector('.water').offsetHeight; const containerHeight = waterContainer.offsetHeight; const yPos = containerHeight - waterHeight; ripple.style.left = `${50 + x/2}%`; ripple.style.top = `${yPos}px`; // Animation ripple.style.animation = 'rippleEffect 1s ease-out forwards'; waterContainer.appendChild(ripple); // Remove ripple after animation setTimeout(() => { ripple.remove(); }, 1000); } // Update loading percentage function updatePercentage() { if (currentPercentage < targetPercentage) { // Non-linear loading to simulate real-world behavior const increment = Math.max(1, Math.floor((targetPercentage - currentPercentage) / 20)); currentPercentage = Math.min(targetPercentage, currentPercentage + increment); loadingPercentage.textContent = `${currentPercentage}%`; // Change color based on progress const blue = Math.floor(150 + (currentPercentage / 100) * 105); loadingPercentage.style.color = `rgb(255, 255, ${blue})`; if (currentPercentage === targetPercentage) { loadingPercentage.style.textShadow = '0 0 30px rgba(96, 165, 250, 0.7)'; clearInterval(loadingInterval); // Add completion animation waterContainer.style.transform = 'scale(1)'; waterContainer.style.boxShadow = '0 0 70px rgba(96, 165, 250, 0.5)'; } } } // Start the loading animation function startLoading() { createParticles(); // Update percentage periodically loadingInterval = setInterval(updatePercentage, 100); // Create droplets periodically const dropletInterval = setInterval(() => { if (currentPercentage < targetPercentage) { createDroplet(); } else { clearInterval(dropletInterval); } }, 200); } startLoading(); // Make the water container interactive on hover waterContainer.addEventListener('mousemove', function(e) { if (currentPercentage === targetPercentage) { const rect = this.getBoundingClientRect(); const x = ((e.clientX - rect.left) / rect.width) * 100 - 50; const y = ((e.clientY - rect.top) / rect.height) * 100 - 50; // Create ripple at hover position if inside the water if (y > 0) { createRipple(x); } } }); }); </script> </body> </html>
<html> <head> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; } body { display: flex; justify-content: center; align-items: center; min-height: 700px; background-color: #f8f9fa; overflow: hidden; padding: 20px; } .container { width: 100%; max-width: 660px; background-color: white; border-radius: 24px; padding: 40px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.05); display: flex; flex-direction: column; gap: 40px; } h1 { font-size: 32px; color: #0a2540; font-weight: 700; margin-bottom: 8px; } p { font-size: 16px; line-height: 1.6; color: #566b8c; max-width: 550px; } .water-container { position: relative; width: 100%; height: 220px; border: 3px solid #0a2540; border-radius: 20px; overflow: hidden; margin-bottom: 20px; } .water { position: absolute; bottom: 0; left: 0; width: 100%; height: 0%; background: linear-gradient(180deg, #7debff 0%, #4b96f3 100%); transition: height 0.3s ease; border-bottom-left-radius: 16px; border-bottom-right-radius: 16px; } .water-overlay { position: absolute; bottom: 0; left: 0; width: 100%; height: 100%; background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="20" viewBox="0 0 100 20"><path d="M0,10 Q25,20 50,10 T100,10" fill="none" stroke="rgba(255,255,255,0.2)" stroke-width="2"/></svg>') repeat-x; background-size: 100px 20px; animation: waveAnimation 3s linear infinite; opacity: 0.7; } .water-overlay:nth-child(2) { animation-delay: -1.5s; animation-duration: 4s; opacity: 0.5; background-size: 80px 20px; } .percentage { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 42px; font-weight: 700; color: #0a2540; z-index: 2; text-shadow: 0 0 10px rgba(255, 255, 255, 0.8); transition: color 0.3s ease; } @keyframes waveAnimation { 0% { background-position: 0 0; } 100% { background-position: 100px 0; } } .controls { display: flex; justify-content: space-between; align-items: center; gap: 20px; } .button-group { display: flex; gap: 12px; } button { padding: 12px 24px; border-radius: 12px; border: none; font-weight: 600; font-size: 15px; cursor: pointer; transition: all 0.2s ease; } button.primary { background-color: #0a2540; color: white; } button.primary:hover { background-color: #1e3a5f; transform: translateY(-2px); } button.secondary { background-color: #e0e7ff; color: #4f46e5; } button.secondary:hover { background-color: #c7d2fe; transform: translateY(-2px); } button.outline { background-color: white; color: #566b8c; border: 1px solid #e2e8f0; } button.outline:hover { background-color: #f8fafc; transform: translateY(-2px); } .milestone-markers { position: absolute; width: 100%; height: 100%; pointer-events: none; } .milestone { position: absolute; width: 100%; left: 0; display: flex; justify-content: space-between; padding: 0 15px; transition: opacity 0.3s ease; } .milestone-dot { width: 8px; height: 8px; background-color: #0a2540; border-radius: 50%; margin-top: -4px; } .milestone-label { font-size: 12px; color: #566b8c; font-weight: 500; padding: 4px 8px; background-color: white; border-radius: 10px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); position: absolute; right: 15px; transform: translateY(-100%); } .task-info { display: flex; justify-content: space-between; align-items: center; margin-top: -10px; } .task-status { background-color: #f0f9ff; color: #0369a1; font-size: 14px; font-weight: 600; padding: 6px 12px; border-radius: 10px; } .task-time { color: #566b8c; font-size: 14px; } .task-time span { font-weight: 600; } .bubbles { position: absolute; bottom: 0; width: 100%; height: 100%; pointer-events: none; } .bubble { position: absolute; bottom: 0; background-color: rgba(255, 255, 255, 0.3); border-radius: 50%; animation: bubbleRise linear forwards; } @keyframes bubbleRise { 0% { transform: translateY(0) scale(0); opacity: 0; } 10% { opacity: 0.8; transform: scale(1); } 90% { opacity: 0.8; } 100% { transform: translateY(-100vh) scale(1); opacity: 0; } } @media (max-width: 640px) { .container { padding: 30px 20px; gap: 25px; } h1 { font-size: 26px; } p { font-size: 14px; } .water-container { height: 180px; } .controls { flex-direction: column; align-items: flex-start; gap: 15px; } .task-info { flex-direction: column; align-items: flex-start; gap: 10px; margin-top: 0; } } </style> </head> <body> <div class="container"> <div class="header"> <h1>Data Migration Progress</h1> <p>Watch as your content flows into the new system in real-time with our fluid progress indicator.</p> </div> <div class="water-container"> <div class="milestone-markers"> <div class="milestone" style="top: 70%;"> <div class="milestone-dot"></div> <div class="milestone-dot"></div> <div class="milestone-label">Prepare Data</div> </div> <div class="milestone" style="top: 40%;"> <div class="milestone-dot"></div> <div class="milestone-dot"></div> <div class="milestone-label">Transfer Files</div> </div> <div class="milestone" style="top: 20%;"> <div class="milestone-dot"></div> <div class="milestone-dot"></div> <div class="milestone-label">Verification</div> </div> </div> <div class="water"> <div class="water-overlay"></div> <div class="water-overlay"></div> <div class="bubbles"></div> </div> <div class="percentage">0%</div> </div> <div class="task-info"> <div class="task-status" id="status">Preparing migration...</div> <div class="task-time">Estimated time: <span id="time-remaining">3 minutes</span></div> </div> <div class="controls"> <div class="button-group"> <button class="primary" id="start">Start Migration</button> <button class="secondary" id="pause">Pause</button> </div> <button class="outline" id="reset">Reset</button> </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { const waterElement = document.querySelector('.water'); const percentageElement = document.querySelector('.percentage'); const startButton = document.getElementById('start'); const pauseButton = document.getElementById('pause'); const resetButton = document.getElementById('reset'); const statusElement = document.getElementById('status'); const timeRemainingElement = document.getElementById('time-remaining'); const bubblesContainer = document.querySelector('.bubbles'); let currentPercentage = 0; let intervalId = null; let isPaused = false; let bubbleIntervalId = null; // Status messages based on percentage const statusMessages = [ { threshold: 0, message: "Preparing migration...", time: "3 minutes" }, { threshold: 30, message: "Transferring files...", time: "2 minutes" }, { threshold: 60, message: "Validating data...", time: "1 minute" }, { threshold: 85, message: "Almost complete", time: "30 seconds" }, { threshold: 100, message: "Migration complete!", time: "Finished" } ]; function updatePercentage(percentage) { currentPercentage = percentage; waterElement.style.height = `${percentage}%`; percentageElement.textContent = `${Math.round(percentage)}%`; // Change text color based on water level if (percentage > 50) { percentageElement.style.color = '#ffffff'; } else { percentageElement.style.color = '#0a2540'; } // Update status message for (let i = statusMessages.length - 1; i >= 0; i--) { if (percentage >= statusMessages[i].threshold) { statusElement.textContent = statusMessages[i].message; timeRemainingElement.textContent = statusMessages[i].time; break; } } } function createBubble() { const bubble = document.createElement('div'); bubble.classList.add('bubble'); // Random properties for natural look const size = Math.random() * 12 + 4; const left = Math.random() * 100; const duration = Math.random() * 3 + 2; const delay = Math.random() * 2; bubble.style.width = `${size}px`; bubble.style.height = `${size}px`; bubble.style.left = `${left}%`; bubble.style.animationDuration = `${duration}s`; bubble.style.animationDelay = `${delay}s`; bubblesContainer.appendChild(bubble); // Remove the bubble after animation completes setTimeout(() => { bubble.remove(); }, (duration + delay) * 1000); } function startBubbles() { if (bubbleIntervalId) clearInterval(bubbleIntervalId); bubbleIntervalId = setInterval(createBubble, 300); } function stopBubbles() { clearInterval(bubbleIntervalId); bubbleIntervalId = null; } startButton.addEventListener('click', function() { if (intervalId !== null) return; if (currentPercentage >= 100) return; isPaused = false; pauseButton.textContent = "Pause"; startBubbles(); intervalId = setInterval(function() { if (currentPercentage < 100) { // Non-linear progress for more realistic effect const increment = 0.3 + (Math.random() * 0.4); updatePercentage(Math.min(currentPercentage + increment, 100)); // Complete at 100% if (currentPercentage >= 100) { clearInterval(intervalId); intervalId = null; setTimeout(stopBubbles, 1000); } } else { clearInterval(intervalId); intervalId = null; } }, 100); }); pauseButton.addEventListener('click', function() { if (intervalId === null && !isPaused) return; if (isPaused) { // Resume isPaused = false; pauseButton.textContent = "Pause"; startBubbles(); intervalId = setInterval(function() { if (currentPercentage < 100) { const increment = 0.3 + (Math.random() * 0.4); updatePercentage(Math.min(currentPercentage + increment, 100)); if (currentPercentage >= 100) { clearInterval(intervalId); intervalId = null; setTimeout(stopBubbles, 1000); } } else { clearInterval(intervalId); intervalId = null; } }, 100); } else { // Pause isPaused = true; pauseButton.textContent = "Resume"; clearInterval(intervalId); intervalId = null; stopBubbles(); } }); resetButton.addEventListener('click', function() { clearInterval(intervalId); intervalId = null; isPaused = false; pauseButton.textContent = "Pause"; stopBubbles(); updatePercentage(0); }); }); </script> </body> </html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Liquid Gallery</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { display: flex; justify-content: center; align-items: center; min-height: 700px; width: 100%; max-width: 700px; margin: 0 auto; background-color: #fafafa; overflow-x: hidden; color: #333; } .gallery-container { width: 100%; max-width: 650px; padding: 20px; display: flex; flex-direction: column; gap: 30px; } .gallery-header { text-align: center; margin-bottom: 10px; } .gallery-header h1 { font-size: 2rem; font-weight: 500; margin-bottom: 10px; color: #2c3e50; letter-spacing: 1px; } .gallery-header p { font-size: 1rem; line-height: 1.5; color: #7f8c8d; max-width: 500px; margin: 0 auto; } .gallery-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; margin-bottom: 20px; } @media (max-width: 500px) { .gallery-grid { grid-template-columns: 1fr; } } .gallery-item { position: relative; overflow: hidden; border-radius: 8px; box-shadow: 0 6px 15px rgba(0, 0, 0, 0.1); transition: transform 0.3s ease; cursor: pointer; aspect-ratio: 1 / 1; } .gallery-item:hover { transform: translateY(-5px); } .gallery-image { position: relative; width: 100%; height: 100%; overflow: hidden; } .gallery-image img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.5s ease; } .gallery-caption { position: absolute; bottom: 0; left: 0; right: 0; padding: 15px; background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent); color: white; transform: translateY(100%); transition: transform 0.3s ease; z-index: 2; } .gallery-item:hover .gallery-caption { transform: translateY(0); } .gallery-caption h3 { font-size: 1.1rem; margin-bottom: 5px; font-weight: 500; } .gallery-caption p { font-size: 0.85rem; line-height: 1.4; opacity: 0.9; } .water-canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 1; opacity: 0; transition: opacity 0.3s ease; } .gallery-item:hover .water-canvas { opacity: 1; } .gallery-controls { display: flex; justify-content: space-between; margin-top: 10px; } .control-btn { background-color: transparent; border: 1px solid #3498db; color: #3498db; padding: 8px 18px; border-radius: 25px; cursor: pointer; font-size: 0.9rem; transition: all 0.3s ease; display: flex; align-items: center; gap: 8px; } .control-btn:hover { background-color: #3498db; color: white; } .control-btn svg { width: 16px; height: 16px; } .lightbox { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.9); display: flex; justify-content: center; align-items: center; z-index: 1000; opacity: 0; visibility: hidden; transition: opacity 0.3s ease, visibility 0.3s ease; } .lightbox.active { opacity: 1; visibility: visible; } .lightbox-content { position: relative; max-width: 90%; max-height: 80%; } .lightbox-image { max-width: 100%; max-height: 80vh; border-radius: 4px; box-shadow: 0 5px 30px rgba(0, 0, 0, 0.3); } .lightbox-close { position: absolute; top: -40px; right: 0; color: white; font-size: 2rem; cursor: pointer; background: none; border: none; outline: none; } .effect-settings { margin-top: 15px; padding: 15px; background-color: white; border-radius: 10px; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); } .effect-settings h3 { margin-bottom: 12px; font-size: 1.1rem; color: #2c3e50; } .slider-container { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; } .slider-container label { min-width: 120px; font-size: 0.9rem; color: #7f8c8d; } .slider { flex-grow: 1; height: 5px; -webkit-appearance: none; background-color: #ddd; border-radius: 5px; outline: none; } .slider::-webkit-slider-thumb { -webkit-appearance: none; width: 15px; height: 15px; border-radius: 50%; background: #3498db; cursor: pointer; } .slider-value { width: 40px; text-align: center; font-size: 0.9rem; color: #7f8c8d; } </style> </head> <body> <div class="gallery-container"> <div class="gallery-header"> <h1>Liquid Moments</h1> <p>Hover over each image to reveal a mesmerizing water ripple effect that transforms ordinary photography into fluid art. Captured moments that flow with your interaction.</p> </div> <div class="gallery-grid"> <div class="gallery-item"> <div class="gallery-image"> <img src="https://images.unsplash.com/photo-1501785888041-af3ef285b470?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=800&q=80" alt="Coastal Sunset"> <canvas class="water-canvas"></canvas> </div> <div class="gallery-caption"> <h3>Coastal Twilight</h3> <p>Waves of orange and blue melt into the horizon at dusk.</p> </div> </div> <div class="gallery-item"> <div class="gallery-image"> <img src="https://images.unsplash.com/photo-1520262454473-a1a82276a574?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=800&q=80" alt="Mountain Lake"> <canvas class="water-canvas"></canvas> </div> <div class="gallery-caption"> <h3>Alpine Mirror</h3> <p>Pristine waters reflect towering peaks in perfect symmetry.</p> </div> </div> <div class="gallery-item"> <div class="gallery-image"> <img src="https://images.unsplash.com/photo-1511884642898-4c92249e20b6?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=800&q=80" alt="Rainforest Stream"> <canvas class="water-canvas"></canvas> </div> <div class="gallery-caption"> <h3>Emerald Currents</h3> <p>Sunlight filters through dense canopy onto flowing waters.</p> </div> </div> <div class="gallery-item"> <div class="gallery-image"> <img src="https://images.unsplash.com/photo-1513407030348-c983a97b98d8?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=800&q=80" alt="Urban Reflection"> <canvas class="water-canvas"></canvas> </div> <div class="gallery-caption"> <h3>Urban Fluidity</h3> <p>City lights dance across rain-soaked streets after dusk.</p> </div> </div> </div> <div class="gallery-controls"> <button class="control-btn" id="prev-btn"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" /> </svg> Previous </button> <button class="control-btn" id="next-btn"> Next <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" /> </svg> </button> </div> <div class="effect-settings"> <h3>Customize Water Effect</h3> <div class="slider-container"> <label for="ripple-strength">Ripple Strength:</label> <input type="range" id="ripple-strength" class="slider" min="0.1" max="5" step="0.1" value="2"> <span class="slider-value" id="ripple-strength-value">2</span> </div> <div class="slider-container"> <label for="ripple-radius">Ripple Size:</label> <input type="range" id="ripple-radius" class="slider" min="5" max="50" step="1" value="20"> <span class="slider-value" id="ripple-radius-value">20</span> </div> <div class="slider-container"> <label for="ripple-speed">Ripple Speed:</label> <input type="range" id="ripple-speed" class="slider" min="0.5" max="5" step="0.1" value="1.5"> <span class="slider-value" id="ripple-speed-value">1.5</span> </div> </div> </div> <div class="lightbox" id="lightbox"> <div class="lightbox-content"> <button class="lightbox-close" id="lightbox-close">Γ</button> <img class="lightbox-image" id="lightbox-image" src="" alt="Enlarged image"> </div> </div> <script> // Water ripple effect parameters let config = { strength: 2, radius: 20, speed: 1.5 }; // Get all gallery items and set up effects const galleryItems = document.querySelectorAll('.gallery-item'); const canvases = document.querySelectorAll('.water-canvas'); // Ripple effect contexts and properties const ripples = []; // Initialize all canvases canvases.forEach((canvas, index) => { const ctx = canvas.getContext('2d'); const parentItem = canvas.closest('.gallery-item'); const img = parentItem.querySelector('img'); // Ensure canvas is properly sized function resizeCanvas() { canvas.width = parentItem.offsetWidth; canvas.height = parentItem.offsetHeight; } resizeCanvas(); window.addEventListener('resize', resizeCanvas); // Create ripple object for this canvas ripples[index] = { ctx: ctx, img: img, canvas: canvas, points: [], last_point: {x: 0, y: 0, z: 0, ts: 0}, animate: false, texture: null }; // Create image texture for ripple effect img.addEventListener('load', () => { const texture = document.createElement('canvas'); texture.width = canvas.width; texture.height = canvas.height; const textureCtx = texture.getContext('2d'); textureCtx.drawImage(img, 0, 0, canvas.width, canvas.height); ripples[index].texture = texture; }); // Event listeners for mouse movement parentItem.addEventListener('mousemove', (e) => { const rect = canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; // Only add new point if we've moved far enough from last point const dx = x - ripples[index].last_point.x; const dy = y - ripples[index].last_point.y; const distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { ripples[index].points.push({ x: x, y: y, radius: 0, maxRadius: config.radius, strength: config.strength, lifetime: 0, maxLifetime: 100 * config.speed }); ripples[index].last_point = {x: x, y: y, ts: Date.now()}; } if (!ripples[index].animate) { ripples[index].animate = true; animateRipple(index); } }); // Start animation when mouse enters parentItem.addEventListener('mouseenter', () => { if (ripples[index].texture) { ripples[index].animate = true; animateRipple(index); } }); // Stop animation when mouse leaves parentItem.addEventListener('mouseleave', () => { ripples[index].animate = false; }); }); // Animation function for a single ripple function animateRipple(index) { if (!ripples[index].animate) return; const ripple = ripples[index]; const ctx = ripple.ctx; if (!ripple.texture) { requestAnimationFrame(() => animateRipple(index)); return; } // Clear canvas ctx.clearRect(0, 0, ripple.canvas.width, ripple.canvas.height); // Update and draw ripple points for (let i = 0; i < ripple.points.length; i++) { const point = ripple.points[i]; point.radius += point.maxRadius / 30 * config.speed; point.lifetime += 1 * config.speed; const opacity = 1 - (point.lifetime / point.maxLifetime); const strength = point.strength * opacity; // Draw ripple effect for (let y = 0; y < ripple.canvas.height; y++) { for (let x = 0; x < ripple.canvas.width; x++) { const dx = x - point.x; const dy = y - point.y; const distance = Math.sqrt(dx * dx + dy * dy); if (distance < point.radius + 10 && distance > point.radius - 10) { const angle = Math.atan2(dy, dx); const amplitude = 10 * strength * Math.sin(distance / 5 - point.lifetime / 3); const ox = amplitude * Math.cos(angle); const oy = amplitude * Math.sin(angle); ctx.drawImage( ripple.texture, x, y, 1, 1, x + ox, y + oy, 1, 1 ); } } } } // Remove expired points ripple.points = ripple.points.filter(p => p.lifetime < p.maxLifetime); requestAnimationFrame(() => animateRipple(index)); } // Optimize ripple effect rendering function optimizedRenderRipple(index) { const ripple = ripples[index]; const ctx = ripple.ctx; if (!ripple.texture) return; // Clear canvas ctx.clearRect(0, 0, ripple.canvas.width, ripple.canvas.height); // Create new canvas for ripple displacement map const displacementMap = document.createElement('canvas'); displacementMap.width = ripple.canvas.width; displacementMap.height = ripple.canvas.height; const displacementCtx = displacementMap.getContext('2d'); // Draw displacement map displacementCtx.clearRect(0, 0, displacementMap.width, displacementMap.height); displacementCtx.fillStyle = 'rgba(128, 128, 0, 1)'; displacementCtx.fillRect(0, 0, displacementMap.width, displacementMap.height); // Add ripple circles to displacement map for (let i = 0; i < ripple.points.length; i++) { const point = ripple.points[i]; const opacity = 1 - (point.lifetime / point.maxLifetime); displacementCtx.beginPath(); const gradient = displacementCtx.createRadialGradient( point.x, point.y, 0, point.x, point.y, point.radius ); gradient.addColorStop(0, `rgba(128, 128, ${255 * point.strength * opacity}, 1)`); gradient.addColorStop(0.8, `rgba(128, 128, ${128 - 128 * point.strength * opacity}, 1)`); gradient.addColorStop(1, 'rgba(128, 128, 0, 0)'); displacementCtx.fillStyle = gradient; displacementCtx.arc(point.x, point.y, point.radius, 0, Math.PI * 2); displacementCtx.fill(); } // Apply displacement map to texture const imageData = displacementCtx.getImageData(0, 0, displacementMap.width, displacementMap.height); const pixels = imageData.data; // Create output image const outputImageData = ctx.createImageData(ripple.canvas.width, ripple.canvas.height); const outputPixels = outputImageData.data; // Apply displacement to texture for (let y = 0; y < ripple.canvas.height; y++) { for (let x = 0; x < ripple.canvas.width; x++) { const i = (y * ripple.canvas.width + x) * 4; // Get displacement from map const dx = (pixels[i] - 128) / 128 * 10; const dy = (pixels[i + 1] - 128) / 128 * 10; // Sample texture with displacement const sx = Math.max(0, Math.min(ripple.canvas.width - 1, x + dx)); const sy = Math.max(0, Math.min(ripple.canvas.height - 1, y + dy)); // Get interpolated pixel const sx1 = Math.floor(sx); const sy1 = Math.floor(sy); const sx2 = Math.min(sx1 + 1, ripple.canvas.width - 1); const sy2 = Math.min(sy1 + 1, ripple.canvas.height - 1); const textureCtx = ripple.texture.getContext('2d'); const texData = textureCtx.getImageData(sx1, sy1, 2, 2).data; // Output interpolated pixel outputPixels[i] = texData[0]; outputPixels[i + 1] = texData[1]; outputPixels[i + 2] = texData[2]; outputPixels[i + 3] = 255; } } ctx.putImageData(outputImageData, 0, 0); } // Handle slider controls const strengthSlider = document.getElementById('ripple-strength'); const radiusSlider = document.getElementById('ripple-radius'); const speedSlider = document.getElementById('ripple-speed'); const strengthValue = document.getElementById('ripple-strength-value'); const radiusValue = document.getElementById('ripple-radius-value'); const speedValue = document.getElementById('ripple-speed-value'); strengthSlider.addEventListener('input', (e) => { config.strength = parseFloat(e.target.value); strengthValue.textContent = config.strength; }); radiusSlider.addEventListener('input', (e) => { config.radius = parseFloat(e.target.value); radiusValue.textContent = config.radius; }); speedSlider.addEventListener('input', (e) => { config.speed = parseFloat(e.target.value); speedValue.textContent = config.speed; }); // Lightbox functionality const lightbox = document.getElementById('lightbox'); const lightboxImg = document.getElementById('lightbox-image'); const lightboxClose = document.getElementById('lightbox-close'); galleryItems.forEach(item => { item.addEventListener('click', () => { const img = item.querySelector('img'); lightboxImg.src = img.src; lightbox.classList.add('active'); }); }); lightboxClose.addEventListener('click', () => { lightbox.classList.remove('active'); }); lightbox.addEventListener('click', (e) => { if (e.target === lightbox) { lightbox.classList.remove('active'); } }); // Gallery navigation const prevBtn = document.getElementById('prev-btn'); const nextBtn = document.getElementById('next-btn'); let currentPage = 0; const itemsPerPage = 4; const totalItems = galleryItems.length; const totalPages = Math.ceil(totalItems / itemsPerPage); function updateGallery() { galleryItems.forEach((item, index) => { const pageStart = currentPage * itemsPerPage; const pageEnd = pageStart + itemsPerPage - 1; if (index >= pageStart && index <= pageEnd) { item.style.display = 'block'; } else { item.style.display = 'none'; } }); } prevBtn.addEventListener('click', () => { currentPage = (currentPage - 1 + totalPages) % totalPages; updateGallery(); }); nextBtn.addEventListener('click', () => { currentPage = (currentPage + 1) % totalPages; updateGallery(); }); // Initialize gallery updateGallery(); // Ensure images are loaded for texture creation window.addEventListener('load', () => { galleryItems.forEach((item, index) => { const img = item.querySelector('img'); if (img.complete) { const canvas = item.querySelector('.water-canvas'); const texture = document.createElement('canvas'); texture.width = canvas.width; texture.height = canvas.height; const textureCtx = texture.getContext('2d'); textureCtx.drawImage(img, 0, 0, canvas.width, canvas.height); ripples[index].texture = texture; } }); }); </script> </body> </html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Water Effects Modal Transitions</title> <style> :root { --primary-blue: #2196F3; --dark-blue: #0D47A1; --light-blue: #BBDEFB; --text-color: #37474F; --ripple-duration: 1.2s; --dissolve-duration: 1s; } * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { background: linear-gradient(135deg, #E3F2FD, #90CAF9); height: 100vh; display: flex; justify-content: center; align-items: center; overflow: hidden; position: relative; } .ocean { position: absolute; bottom: 0; left: 0; width: 100%; height: 40%; background: linear-gradient(180deg, rgba(3, 169, 244, 0.8) 0%, rgba(13, 71, 161, 0.9) 100%); overflow: hidden; z-index: 1; transform-origin: bottom; } .wave { position: absolute; top: -25px; left: 0; width: 200%; height: 50px; background: url('data:image/svg+xml;utf8,<svg viewBox="0 0 350 50" xmlns="http://www.w3.org/2000/svg"><path d="M0,0 L0,50 L350,50 L350,0 Q175,55 0,0 Z" fill="%230D47A1" opacity="0.7"/></svg>'); background-repeat: repeat-x; background-size: 350px 50px; animation: wave 15s linear infinite; } .wave:nth-child(2) { top: -20px; opacity: 0.5; animation: wave 17s linear infinite reverse; } .wave:nth-child(3) { top: -15px; opacity: 0.3; animation: wave 20s linear infinite; } @keyframes wave { 0% { transform: translateX(0); } 100% { transform: translateX(-50%); } } .container { max-width: 600px; width: 100%; padding: 20px; text-align: center; z-index: 2; } h1 { color: var(--dark-blue); margin-bottom: 20px; font-weight: 700; font-size: 2.5rem; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .subtitle { color: var(--text-color); margin-bottom: 30px; font-size: 1.1rem; line-height: 1.6; } .btn-open { background-color: var(--primary-blue); color: white; border: none; padding: 12px 24px; font-size: 1rem; border-radius: 50px; cursor: pointer; box-shadow: 0 4px 12px rgba(33, 150, 243, 0.3); transition: all 0.3s ease; position: relative; overflow: hidden; } .btn-open:hover { transform: translateY(-2px); box-shadow: 0 6px 16px rgba(33, 150, 243, 0.4); } .btn-open:active { transform: translateY(0); } /* Button water ripple effect */ .btn-open::after { content: ''; position: absolute; top: 50%; left: 50%; width: 5px; height: 5px; background: rgba(255, 255, 255, 0.5); opacity: 0; border-radius: 100%; transform: scale(1, 1) translate(-50%, -50%); transform-origin: 50% 50%; } .btn-open:focus:not(:active)::after { animation: ripple 1s ease-out; } @keyframes ripple { 0% { transform: scale(0, 0); opacity: 0.5; } 100% { transform: scale(100, 100); opacity: 0; } } .modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(3, 169, 244, 0.2); backdrop-filter: blur(3px); z-index: 998; display: flex; justify-content: center; align-items: center; opacity: 0; pointer-events: none; transition: opacity 0.5s ease; } .modal-overlay.active { opacity: 1; pointer-events: all; } .modal { background: white; width: 90%; max-width: 500px; border-radius: 12px; box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12); overflow: hidden; transform: scale(0.8); opacity: 0; transition: transform 0.4s ease, opacity 0.4s ease; position: relative; z-index: 999; } .modal.active { transform: scale(1); opacity: 1; } .modal-header { background: linear-gradient(135deg, var(--primary-blue), var(--dark-blue)); color: white; padding: 20px; position: relative; overflow: hidden; } .modal-body { padding: 25px; color: var(--text-color); } .modal-title { font-size: 1.5rem; margin-bottom: 5px; } .modal-subtitle { font-size: 0.9rem; opacity: 0.8; } .modal-content { margin: 15px 0; line-height: 1.6; } .modal-features { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-top: 20px; } .feature-item { background-color: #F5F9FF; padding: 15px; border-radius: 8px; display: flex; flex-direction: column; align-items: center; text-align: center; transition: transform 0.3s ease, box-shadow 0.3s ease; } .feature-item:hover { transform: translateY(-5px); box-shadow: 0 6px 15px rgba(33, 150, 243, 0.15); } .feature-icon { background-color: var(--light-blue); width: 50px; height: 50px; border-radius: 50%; display: flex; justify-content: center; align-items: center; margin-bottom: 10px; } .feature-title { font-weight: 600; margin-bottom: 5px; } .feature-desc { font-size: 0.85rem; color: #607D8B; } .modal-footer { padding: 15px 25px; display: flex; justify-content: flex-end; gap: 15px; border-top: 1px solid #EEEEEE; } .btn-close { background-color: white; color: var(--primary-blue); border: 1px solid var(--primary-blue); padding: 10px 20px; font-size: 0.9rem; border-radius: 50px; cursor: pointer; transition: all 0.3s ease; } .btn-close:hover { background-color: #F5F9FF; } .btn-learn-more { background-color: var(--primary-blue); color: white; border: none; padding: 10px 20px; font-size: 0.9rem; border-radius: 50px; cursor: pointer; transition: all 0.3s ease; } .btn-learn-more:hover { background-color: #1976D2; } /* Water transition effects */ .ripple-container { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 997; pointer-events: none; overflow: hidden; } .ripple { position: absolute; border-radius: 50%; background-color: rgba(33, 150, 243, 0.15); transform: scale(0); will-change: transform; } .ripple.animate { animation: ripple-effect var(--ripple-duration) cubic-bezier(0.215, 0.61, 0.355, 1) forwards; } @keyframes ripple-effect { 0% { transform: scale(0); opacity: 0.5; } 100% { transform: scale(4); opacity: 0; } } /* Dissolve animation for modal */ .dissolve-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient(to bottom, transparent, rgba(13, 71, 161, 0.8)); opacity: 0; z-index: 0; pointer-events: none; } .dissolve-bubbles { position: absolute; width: 100%; height: 100%; overflow: hidden; opacity: 0; pointer-events: none; } .bubble { position: absolute; background-color: rgba(255, 255, 255, 0.2); border-radius: 50%; animation: float-up 3s ease-in infinite; } @keyframes float-up { 0% { transform: translateY(100%) scale(0); opacity: 0.5; } 50% { opacity: 0.7; } 100% { transform: translateY(-100vh) scale(1); opacity: 0; } } /* Responsive styles */ @media (max-width: 600px) { h1 { font-size: 2rem; } .modal-features { grid-template-columns: 1fr; } .modal-footer { flex-direction: column; } .btn-close, .btn-learn-more { width: 100%; } } @media (max-height: 700px) { .ocean { height: 30%; } } </style> </head> <body> <div class="ocean"> <div class="wave"></div> <div class="wave"></div> <div class="wave"></div> </div> <div class="container"> <h1>Fluid Transitions</h1> <p class="subtitle">Experience our innovative water-based modal transitions, where interface elements emerge and dissolve like ripples on a calm sea surface. Perfect for creating an immersive user experience.</p> <button class="btn-open" id="openModal">Open Water Modal</button> </div> <div class="modal-overlay" id="modalOverlay"> <div class="modal" id="modal"> <div class="modal-header"> <h2 class="modal-title">Water Transition Effects</h2> <p class="modal-subtitle">Fluid animations for modern interfaces</p> <div class="dissolve-overlay" id="dissolveOverlay"></div> <div class="dissolve-bubbles" id="dissolveBubbles"></div> </div> <div class="modal-body"> <p class="modal-content"> Our water-based modal transitions create a seamless and engaging experience as components emerge from and dissolve back into the interface. These transitions are designed to be both visually appealing and functional. </p> <div class="modal-features"> <div class="feature-item"> <div class="feature-icon">π§</div> <h3 class="feature-title">Fluid Motion</h3> <p class="feature-desc">Natural movement patterns that mimic water physics</p> </div> <div class="feature-item"> <div class="feature-icon">π</div> <h3 class="feature-title">Wave Effects</h3> <p class="feature-desc">Rippling animations that guide user attention</p> </div> <div class="feature-item"> <div class="feature-icon">π</div> <h3 class="feature-title">Seamless Transitions</h3> <p class="feature-desc">Smooth entrances and exits between interface states</p> </div> </div> </div> <div class="modal-footer"> <button class="btn-close" id="closeModal">Close</button> <button class="btn-learn-more">Learn More</button> </div> </div> </div> <div class="ripple-container" id="rippleContainer"></div> <script> document.addEventListener('DOMContentLoaded', function() { const openModalBtn = document.getElementById('openModal'); const closeModalBtn = document.getElementById('closeModal'); const modal = document.getElementById('modal'); const modalOverlay = document.getElementById('modalOverlay'); const rippleContainer = document.getElementById('rippleContainer'); const dissolveOverlay = document.getElementById('dissolveOverlay'); const dissolveBubbles = document.getElementById('dissolveBubbles'); // Add floating bubbles to the ocean function createOceanBubbles() { const ocean = document.querySelector('.ocean'); for (let i = 0; i < 15; i++) { const bubble = document.createElement('div'); bubble.classList.add('bubble'); bubble.style.width = `${Math.random() * 20 + 5}px`; bubble.style.height = bubble.style.width; bubble.style.left = `${Math.random() * 100}%`; bubble.style.animationDuration = `${Math.random() * 5 + 3}s`; bubble.style.animationDelay = `${Math.random() * 2}s`; ocean.appendChild(bubble); } } createOceanBubbles(); // Create ripple effect function createRipple(e) { const ripple = document.createElement('div'); ripple.classList.add('ripple'); const button = e.currentTarget; const buttonRect = button.getBoundingClientRect(); // Use the button's center as the ripple starting point const size = Math.max(window.innerWidth, window.innerHeight) * 2; ripple.style.width = `${size}px`; ripple.style.height = `${size}px`; ripple.style.left = `${buttonRect.left + buttonRect.width / 2}px`; ripple.style.top = `${buttonRect.top + buttonRect.height / 2}px`; ripple.style.marginLeft = `-${size / 2}px`; ripple.style.marginTop = `-${size / 2}px`; rippleContainer.appendChild(ripple); // Start the animation setTimeout(() => { ripple.classList.add('animate'); }, 0); // Remove the ripple element after animation completes setTimeout(() => { ripple.remove(); }, parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--ripple-duration')) * 1000); } // Create dissolve effect bubbles for closing modal function createDissolveBubbles() { dissolveBubbles.innerHTML = ''; for (let i = 0; i < 20; i++) { const bubble = document.createElement('div'); bubble.classList.add('bubble'); bubble.style.width = `${Math.random() * 50 + 10}px`; bubble.style.height = bubble.style.width; bubble.style.left = `${Math.random() * 100}%`; bubble.style.top = `${Math.random() * 100}%`; bubble.style.animationDuration = `${Math.random() * 2 + 1}s`; bubble.style.animationDelay = `${Math.random() * 0.5}s`; dissolveBubbles.appendChild(bubble); } } // Open modal with water effect openModalBtn.addEventListener('click', function(e) { createRipple(e); // Delay the modal showing to sync with ripple effect setTimeout(() => { modalOverlay.classList.add('active'); // Small delay for the modal to appear after overlay setTimeout(() => { modal.classList.add('active'); }, 100); }, 300); }); // Close modal with dissolve effect closeModalBtn.addEventListener('click', function() { // Create dissolve bubbles createDissolveBubbles(); dissolveOverlay.style.opacity = '1'; dissolveBubbles.style.opacity = '1'; // Animate modal out modal.classList.remove('active'); // Wait for modal to complete its exit animation setTimeout(() => { modalOverlay.classList.remove('active'); // Reset dissolve effects after modal is closed setTimeout(() => { dissolveOverlay.style.opacity = '0'; dissolveBubbles.style.opacity = '0'; }, 500); }, 400); }); // Also close when clicking on overlay modalOverlay.addEventListener('click', function(e) { if (e.target === modalOverlay) { closeModalBtn.click(); } }); // Prevent propagation from modal to overlay modal.addEventListener('click', function(e) { e.stopPropagation(); }); }); </script> </body> </html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { display: flex; justify-content: center; align-items: center; min-height: 700px; background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); padding: 20px; overflow: hidden; } .container { width: 100%; max-width: 660px; background-color: #ffffff; border-radius: 24px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1); padding: 40px; position: relative; overflow: hidden; transition: all 0.5s ease; } header { text-align: center; margin-bottom: 30px; } h1 { font-weight: 700; font-size: 28px; color: #1a3c5e; margin-bottom: 15px; } p.subtitle { color: #6b7c93; font-size: 16px; line-height: 1.6; max-width: 500px; margin: 0 auto 30px; } .icons-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 30px; justify-content: center; align-items: center; margin-bottom: 40px; } .icon-item { display: flex; flex-direction: column; align-items: center; cursor: pointer; position: relative; transition: transform 0.3s ease; } .icon-wrapper { position: relative; width: 70px; height: 70px; display: flex; justify-content: center; align-items: center; border-radius: 50%; background-color: #f7f9fc; margin-bottom: 12px; overflow: hidden; transition: all 0.3s ease; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05); } .icon-label { font-size: 14px; color: #1a3c5e; font-weight: 500; text-align: center; } .icon { color: #4a90e2; font-size: 28px; z-index: 2; transition: transform 0.4s ease, color 0.4s ease; } /* Water effect styles */ .water-effect { position: absolute; width: 100%; height: 100%; background: radial-gradient(circle, rgba(74, 144, 226, 0.1) 0%, rgba(74, 144, 226, 0) 70%); border-radius: 50%; opacity: 0; transform: scale(0); pointer-events: none; z-index: 1; } .ripple { position: absolute; background: rgba(74, 144, 226, 0.2); border-radius: 50%; transform: scale(0); animation: none; pointer-events: none; } @keyframes ripple { to { transform: scale(2.5); opacity: 0; } } .droplet { position: absolute; width: 10px; height: 15px; background-color: rgba(74, 144, 226, 0.4); border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%; transform: translateY(-30px) scale(0); opacity: 0; z-index: 3; } @keyframes droplet-fall { 0% { transform: translateY(-30px) scale(0); opacity: 0; } 30% { transform: translateY(0) scale(1); opacity: 1; } 100% { transform: translateY(0) scale(0); opacity: 0; } } .info-section { margin-top: 50px; padding: 20px; background-color: #f7f9fc; border-radius: 12px; position: relative; overflow: hidden; } .info-title { font-size: 18px; font-weight: 600; color: #1a3c5e; margin-bottom: 10px; } .info-text { font-size: 14px; color: #6b7c93; line-height: 1.6; } .tag { display: inline-block; background-color: rgba(74, 144, 226, 0.1); color: #4a90e2; padding: 4px 10px; border-radius: 100px; font-size: 12px; margin-right: 8px; margin-bottom: 8px; font-weight: 500; } .tags { margin-top: 15px; } .mode-switcher { position: absolute; top: 20px; right: 20px; width: 50px; height: 26px; background-color: #f7f9fc; border-radius: 30px; cursor: pointer; padding: 3px; transition: background-color 0.3s ease; } .toggle { width: 20px; height: 20px; background-color: #4a90e2; border-radius: 50%; transition: transform 0.3s ease; } body.dark-mode { background: linear-gradient(135deg, #1a2a3a 0%, #0e1621 100%); } body.dark-mode .container { background-color: #1e2a3a; } body.dark-mode h1, body.dark-mode .icon-label, body.dark-mode .info-title { color: #e1e8f0; } body.dark-mode p.subtitle, body.dark-mode .info-text { color: #a0aec0; } body.dark-mode .icon-wrapper { background-color: #2d3748; } body.dark-mode .info-section { background-color: #2d3748; } body.dark-mode .mode-switcher { background-color: #2d3748; } body.dark-mode .toggle { transform: translateX(24px); background-color: #4a90e2; } @media (max-width: 600px) { .container { padding: 25px; border-radius: 16px; } h1 { font-size: 24px; } .icons-grid { gap: 20px; grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); } .icon-wrapper { width: 60px; height: 60px; } .info-section { padding: 15px; } } </style> </head> <body> <div class="container"> <div class="mode-switcher"> <div class="toggle"></div> </div> <header> <h1>Fluid Interaction Icons</h1> <p class="subtitle">Experience the subtle beauty of fluid dynamics with our responsive icon set. Hover over each icon to see a realistic water splash effect.</p> </header> <div class="icons-grid"> <div class="icon-item"> <div class="icon-wrapper"> <i class="icon">π±</i> <div class="water-effect"></div> </div> <span class="icon-label">Mobile</span> </div> <div class="icon-item"> <div class="icon-wrapper"> <i class="icon">π»</i> <div class="water-effect"></div> </div> <span class="icon-label">Desktop</span> </div> <div class="icon-item"> <div class="icon-wrapper"> <i class="icon">π</i> <div class="water-effect"></div> </div> <span class="icon-label">Search</span> </div> <div class="icon-item"> <div class="icon-wrapper"> <i class="icon">π</i> <div class="water-effect"></div> </div> <span class="icon-label">Edit</span> </div> <div class="icon-item"> <div class="icon-wrapper"> <i class="icon">π</i> <div class="water-effect"></div> </div> <span class="icon-label">Sync</span> </div> <div class="icon-item"> <div class="icon-wrapper"> <i class="icon">π</i> <div class="water-effect"></div> </div> <span class="icon-label">Analytics</span> </div> <div class="icon-item"> <div class="icon-wrapper"> <i class="icon">βοΈ</i> <div class="water-effect"></div> </div> <span class="icon-label">Settings</span> </div> <div class="icon-item"> <div class="icon-wrapper"> <i class="icon">π</i> <div class="water-effect"></div> </div> <span class="icon-label">Files</span> </div> </div> <div class="info-section"> <h3 class="info-title">About Surface Tension Effects</h3> <p class="info-text">The micro-animations you're experiencing mimic the physics of water droplets disturbing a calm surface. Each hover creates a realistic surface tension response that provides tactile feedback without disrupting the clean interface.</p> <div class="tags"> <span class="tag">Micro-interactions</span> <span class="tag">Fluid Physics</span> <span class="tag">Modern UI</span> <span class="tag">Interactive Design</span> </div> </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { const iconWrappers = document.querySelectorAll('.icon-wrapper'); const modeSwitcher = document.querySelector('.mode-switcher'); // Dark/light mode toggle modeSwitcher.addEventListener('click', function() { document.body.classList.toggle('dark-mode'); }); // Water effect function iconWrappers.forEach(wrapper => { wrapper.addEventListener('mouseenter', function(e) { // Create water ripple effect const ripple = document.createElement('div'); ripple.classList.add('ripple'); this.appendChild(ripple); // Get wrapper dimensions const size = Math.max(this.offsetWidth, this.offsetHeight); const pos = this.getBoundingClientRect(); // Position and size the ripple ripple.style.width = ripple.style.height = `${size}px`; ripple.style.left = `${e.clientX - pos.left - size/2}px`; ripple.style.top = `${e.clientY - pos.top - size/2}px`; // Animate ripple ripple.style.animation = 'ripple 0.7s ease-out forwards'; // Animate the water effect background const waterEffect = this.querySelector('.water-effect'); waterEffect.style.opacity = '1'; waterEffect.style.transform = 'scale(1)'; // Create droplet effect const droplet = document.createElement('div'); droplet.classList.add('droplet'); this.appendChild(droplet); // Position droplet droplet.style.left = '50%'; droplet.style.top = '50%'; droplet.style.marginLeft = '-5px'; // Animate droplet droplet.style.animation = 'droplet-fall 0.5s ease-out forwards'; // Scale the icon slightly const icon = this.querySelector('.icon'); icon.style.transform = 'scale(1.1)'; icon.style.color = '#4a90e2'; // Remove elements after animation completes setTimeout(() => { ripple.remove(); droplet.remove(); }, 700); }); wrapper.addEventListener('mouseleave', function() { // Reset water effect const waterEffect = this.querySelector('.water-effect'); waterEffect.style.opacity = '0'; waterEffect.style.transform = 'scale(0)'; // Reset icon const icon = this.querySelector('.icon'); icon.style.transform = 'scale(1)'; icon.style.color = '#4a90e2'; }); }); // Add fancy hover effect for the icons iconWrappers.forEach(wrapper => { wrapper.addEventListener('mousemove', function(e) { const rect = this.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; // Calculate distance from center const centerX = rect.width / 2; const centerY = rect.height / 2; // Subtle tilt effect const tiltX = (y - centerY) / 10; const tiltY = (centerX - x) / 10; this.style.transform = `perspective(300px) rotateX(${tiltX}deg) rotateY(${tiltY}deg)`; }); wrapper.addEventListener('mouseleave', function() { this.style.transform = 'perspective(300px) rotateX(0) rotateY(0)'; }); }); }); </script> </body> </html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Fluid Dashboard Background</title> <style> :root { --deep-blue: #0a2e4a; --medium-blue: #1a5b8a; --light-blue: #3aa0d1; --highlight-blue: #7ccff5; --foam: rgba(255, 255, 255, 0.7); } * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; color: white; background-color: var(--deep-blue); overflow: hidden; height: 100vh; width: 100%; display: flex; flex-direction: column; align-items: center; justify-content: flex-start; } .dashboard-container { position: relative; width: 100%; max-width: 700px; height: 700px; z-index: 2; padding: 25px; display: flex; flex-direction: column; } .water-background { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; background: var(--deep-blue); overflow: hidden; } .wave { position: absolute; width: 200%; height: 200%; border-radius: 40%; opacity: 0.4; animation: drift linear infinite; transform-origin: 50% 48%; } .wave1 { background: var(--medium-blue); animation-duration: 25s; left: -50%; top: -70%; } .wave2 { background: var(--light-blue); animation-duration: 20s; animation-delay: -5s; left: -30%; top: -50%; } .wave3 { background: var(--highlight-blue); animation-duration: 15s; animation-delay: -2s; left: -45%; top: -60%; } @keyframes drift { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } .bubble { position: absolute; border-radius: 50%; background: var(--foam); animation: bubble-rise ease-in-out forwards; opacity: 0; } @keyframes bubble-rise { 0% { transform: translateY(100%) scale(0.5); opacity: 0; } 20% { opacity: 0.4; } 80% { opacity: 0.4; } 100% { transform: translateY(-100vh) scale(1.2); opacity: 0; } } .header { margin-bottom: 25px; text-align: center; position: relative; z-index: 10; } .header h1 { font-size: 2.2rem; margin-bottom: 0.5rem; font-weight: 300; letter-spacing: 1px; color: white; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); } .header p { font-size: 1rem; opacity: 0.9; max-width: 600px; margin: 0 auto; color: var(--highlight-blue); } .card-grid { display: grid; grid-template-columns: repeat(2, 1fr); grid-gap: 20px; width: 100%; margin-top: 15px; z-index: 10; position: relative; } .card { background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(10px); border-radius: 12px; padding: 20px; transition: all 0.3s ease; cursor: pointer; border: 1px solid rgba(255, 255, 255, 0.1); display: flex; flex-direction: column; align-items: center; } .card:hover { transform: translateY(-5px); box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2); background: rgba(255, 255, 255, 0.15); border-color: var(--highlight-blue); } .card-value { font-size: 2rem; font-weight: 700; margin: 10px 0; color: white; } .card-title { font-size: 1rem; color: var(--highlight-blue); text-transform: uppercase; letter-spacing: 1px; margin-bottom: 5px; } .card-trend { font-size: 0.9rem; margin-top: 5px; } .positive { color: #4caf50; } .negative { color: #f44336; } .neutral { color: #ff9800; } .chart-container { width: 100%; height: 300px; margin-top: 25px; background: rgba(255, 255, 255, 0.05); border-radius: 12px; padding: 20px; position: relative; z-index: 10; border: 1px solid rgba(255, 255, 255, 0.1); overflow: hidden; } .chart-title { font-size: 1.2rem; margin-bottom: 15px; color: white; font-weight: 500; } .chart { width: 100%; height: 220px; display: flex; align-items: flex-end; justify-content: space-around; padding-top: 20px; } .bar { width: 30px; background: linear-gradient(to top, var(--medium-blue), var(--highlight-blue)); border-radius: 4px 4px 0 0; position: relative; transition: height 0.5s ease; min-height: 20px; cursor: pointer; } .bar:hover { background: linear-gradient(to top, var(--light-blue), white); filter: brightness(1.2); } .bar-label { position: absolute; bottom: -25px; left: 50%; transform: translateX(-50%); font-size: 0.85rem; color: rgba(255, 255, 255, 0.8); white-space: nowrap; } .bar-value { position: absolute; top: -25px; left: 50%; transform: translateX(-50%); font-size: 0.85rem; color: var(--highlight-blue); opacity: 0; transition: opacity 0.3s ease; } .bar:hover .bar-value { opacity: 1; } .water-depth-indicator { position: absolute; bottom: 20px; right: 20px; background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(5px); border-radius: 50%; width: 80px; height: 80px; display: flex; flex-direction: column; align-items: center; justify-content: center; border: 1px solid rgba(255, 255, 255, 0.2); z-index: 100; transition: all 0.3s ease; } .water-depth-indicator:hover { transform: scale(1.1); background: rgba(255, 255, 255, 0.2); } .depth-value { font-size: 1.5rem; font-weight: 700; color: white; } .depth-label { font-size: 0.7rem; color: var(--highlight-blue); text-transform: uppercase; letter-spacing: 1px; } @media (max-width: 600px) { .dashboard-container { padding: 15px; } .header h1 { font-size: 1.8rem; } .card-grid { grid-template-columns: 1fr; } .chart-container { height: 250px; } .chart { height: 170px; } .bar { width: 20px; } } </style> </head> <body> <div class="water-background"> <div class="wave wave1"></div> <div class="wave wave2"></div> <div class="wave wave3"></div> <!-- Bubbles will be added via JavaScript --> </div> <div class="dashboard-container"> <div class="header"> <h1>Ocean Data Analytics</h1> <p>Real-time monitoring of subsurface conditions with reduced visual noise for enhanced focus</p> </div> <div class="card-grid"> <div class="card"> <div class="card-title">Water Clarity</div> <div class="card-value">87%</div> <div class="card-trend positive">β 3.2% from last week</div> </div> <div class="card"> <div class="card-title">Salinity</div> <div class="card-value">34.8 PSU</div> <div class="card-trend neutral">β Stable conditions</div> </div> <div class="card"> <div class="card-title">Temperature</div> <div class="card-value">22.4Β°C</div> <div class="card-trend negative">β 1.1Β°C this month</div> </div> <div class="card"> <div class="card-title">Dissolved Oxygen</div> <div class="card-value">6.8 mg/L</div> <div class="card-trend positive">β 0.3 mg/L improvement</div> </div> </div> <div class="chart-container"> <div class="chart-title">Depth Profile Variations (Last 7 Days)</div> <div class="chart"> <!-- Bars will be added via JavaScript --> </div> </div> <div class="water-depth-indicator"> <div class="depth-value">723m</div> <div class="depth-label">Current Depth</div> </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { // Generate bubbles const waterBackground = document.querySelector('.water-background'); function createBubble() { const bubble = document.createElement('div'); bubble.classList.add('bubble'); // Random size const size = Math.random() * 15 + 5; bubble.style.width = `${size}px`; bubble.style.height = `${size}px`; // Random horizontal position const posX = Math.random() * 100; bubble.style.left = `${posX}%`; // Random animation duration const duration = Math.random() * 8 + 4; bubble.style.animationDuration = `${duration}s`; // Remove bubble after animation completes bubble.addEventListener('animationend', function() { this.remove(); }); waterBackground.appendChild(bubble); } // Create initial bubbles for (let i = 0; i < 10; i++) { setTimeout(createBubble, Math.random() * 2000); } // Continue creating bubbles at intervals setInterval(createBubble, 800); // Create chart bars with dynamic heights const chartData = [ { day: 'Mon', value: 63 }, { day: 'Tue', value: 48 }, { day: 'Wed', value: 72 }, { day: 'Thu', value: 92 }, { day: 'Fri', value: 85 }, { day: 'Sat', value: 76 }, { day: 'Sun', value: 89 } ]; const chart = document.querySelector('.chart'); chartData.forEach(item => { const bar = document.createElement('div'); bar.classList.add('bar'); bar.style.height = `${item.value}%`; const label = document.createElement('div'); label.classList.add('bar-label'); label.textContent = item.day; const value = document.createElement('div'); value.classList.add('bar-value'); value.textContent = `${item.value}m`; bar.appendChild(label); bar.appendChild(value); chart.appendChild(bar); }); // Animate water depth indicator const depthValue = document.querySelector('.depth-value'); let depth = 723; let direction = 1; setInterval(() => { depth += direction * (Math.random() * 2); if (depth > 730) direction = -1; if (depth < 720) direction = 1; depthValue.textContent = `${Math.round(depth)}m`; }, 2000); }); </script> </body> </html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Water Droplet Notifications</title> <style> :root { --primary: #2196F3; --primary-light: #BBDEFB; --dark: #1976D2; --white: #FFFFFF; --shadow: rgba(0, 0, 0, 0.1); } * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; overflow: hidden; position: relative; } .container { width: 100%; max-width: 700px; height: 700px; display: flex; flex-direction: column; justify-content: space-between; padding: 20px; position: relative; } .app-header { text-align: center; margin-bottom: 20px; color: #333; } .phone-mock { width: 280px; height: 560px; background-color: var(--white); border-radius: 30px; margin: 0 auto; padding: 20px; box-shadow: 0 10px 30px var(--shadow); position: relative; overflow: hidden; border: 8px solid #333; } .phone-content { height: 100%; display: flex; flex-direction: column; justify-content: flex-start; align-items: center; padding-top: 20px; background: linear-gradient(to bottom, #e0f7fa, #b2ebf2); } .phone-time { font-size: 32px; font-weight: 300; margin-bottom: 10px; } .phone-date { font-size: 14px; opacity: 0.7; margin-bottom: 30px; } .app-icons { display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; width: 100%; } .app-icon { width: 50px; height: 50px; border-radius: 12px; display: flex; align-items: center; justify-content: center; color: white; font-size: 20px; box-shadow: 0 3px 6px rgba(0, 0, 0, 0.1); } .app-icon:nth-child(1) { background-color: #FF5722; } .app-icon:nth-child(2) { background-color: #4CAF50; } .app-icon:nth-child(3) { background-color: #2196F3; } .app-icon:nth-child(4) { background-color: #9C27B0; } .app-icon:nth-child(5) { background-color: #FF9800; } .app-icon:nth-child(6) { background-color: #795548; } .app-icon:nth-child(7) { background-color: #607D8B; } .app-icon:nth-child(8) { background-color: #E91E63; } .controls { margin-top: 30px; display: flex; flex-direction: column; gap: 15px; width: 280px; } .demo-btn { width: 100%; padding: 12px 20px; background-color: var(--primary); color: white; border: none; border-radius: 12px; font-size: 16px; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 4px 8px rgba(33, 150, 243, 0.3); } .demo-btn:hover { background-color: var(--dark); transform: translateY(-2px); box-shadow: 0 6px 12px rgba(33, 150, 243, 0.4); } .demo-btn:active { transform: translateY(0); box-shadow: 0 2px 4px rgba(33, 150, 243, 0.3); } .notification-container { position: absolute; width: 100%; pointer-events: none; } .top-container { top: 0; left: 0; } .bottom-container { bottom: 0; left: 0; } .water-droplet { position: absolute; width: 12px; height: 12px; background-color: var(--primary); border-radius: 50%; opacity: 0; transform: scale(0); } .water-notification { background-color: rgba(255, 255, 255, 0.9); border-radius: 12px; padding: 16px; margin: 10px; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); transform: translateY(-100%); opacity: 0; width: calc(100% - 20px); max-width: 350px; display: flex; justify-content: space-between; align-items: flex-start; position: relative; overflow: hidden; } .water-notification.bottom { transform: translateY(100%); } .notification-icon { width: 40px; height: 40px; border-radius: 10px; display: flex; align-items: center; justify-content: center; margin-right: 12px; flex-shrink: 0; font-size: 20px; color: white; } .notification-content { flex-grow: 1; } .notification-app { font-weight: 600; font-size: 14px; margin-bottom: 4px; } .notification-message { font-size: 13px; color: #333; line-height: 1.4; } .notification-time { font-size: 11px; color: #777; margin-top: 4px; } .notification-ripple { position: absolute; top: 0; left: 0; width: 100%; height: 5px; background: linear-gradient(90deg, transparent, var(--primary), transparent); opacity: 0.7; } .instructions { text-align: center; margin-top: 20px; color: #555; font-size: 14px; max-width: 400px; line-height: 1.5; } @media (max-width: 700px) { .container { padding: 10px; } .phone-mock { width: 240px; height: 480px; } .controls { width: 240px; } } </style> </head> <body> <div class="container"> <h1 class="app-header">Water Droplet Notifications</h1> <div class="phone-mock"> <div class="phone-content"> <div class="phone-time" id="phone-time">10:23</div> <div class="phone-date" id="phone-date">Sunday, March 5</div> <div class="app-icons"> <div class="app-icon">π±</div> <div class="app-icon">π§</div> <div class="app-icon">π·</div> <div class="app-icon">π΅</div> <div class="app-icon">πΊοΈ</div> <div class="app-icon">π</div> <div class="app-icon">π</div> <div class="app-icon">βοΈ</div> </div> </div> <div class="notification-container top-container" id="top-container"></div> <div class="notification-container bottom-container" id="bottom-container"></div> </div> <div class="controls"> <button class="demo-btn" id="top-btn">Raindrops from Top</button> <button class="demo-btn" id="bottom-btn">Ripples from Bottom</button> </div> <p class="instructions"> Click the buttons to see water-inspired notification animations. Top notifications mimic raindrops falling from above, while bottom ones create ripple effects as if emerging from water. Both use natural fluid motion to minimize disruption. </p> </div> <script> // Update phone time and date function updatePhoneTime() { const now = new Date(); const hours = now.getHours().toString().padStart(2, '0'); const minutes = now.getMinutes().toString().padStart(2, '0'); document.getElementById('phone-time').textContent = `${hours}:${minutes}`; const options = { weekday: 'long', month: 'long', day: 'numeric' }; document.getElementById('phone-date').textContent = now.toLocaleDateString('en-US', options); } updatePhoneTime(); setInterval(updatePhoneTime, 60000); // Notification data const notificationData = [ { app: "Weather", icon: "π§οΈ", color: "#1976D2", messages: [ "Light rain expected this afternoon. Don't forget your umbrella!", "Humidity levels rising to 85%. Expect a misty morning tomorrow.", "Flash flood warning in your area. Stay safe and avoid low areas." ] }, { app: "Hydration Tracker", icon: "π§", color: "#03A9F4", messages: [ "Time for a water break! You're 2 cups behind your daily goal.", "Congratulations! You've reached 80% of your hydration goal today.", "Hot day ahead! Remember to increase your water intake." ] }, { app: "Ocean Sounds", icon: "π", color: "#0288D1", messages: [ "Your evening wave sounds are ready to play for better sleep.", "New rainfall ambient playlist added to your library.", "Your sleep quality improved 23% with water sounds last night!" ] }, { app: "Garden Helper", icon: "π±", color: "#4CAF50", messages: [ "Your plants are thirsty! Time to water your ferns.", "Rain detected! No need to water your garden today.", "Soil moisture levels optimal after yesterday's watering." ] } ]; // Create water droplet animation function createWaterDroplet(position, direction) { const phoneRect = document.querySelector('.phone-mock').getBoundingClientRect(); const container = document.getElementById(`${direction}-container`); // Create multiple droplets for a natural effect for (let i = 0; i < Math.floor(Math.random() * 3) + 3; i++) { const droplet = document.createElement('div'); droplet.classList.add('water-droplet'); // Random position horizontally const xPos = Math.random() * (phoneRect.width - 20) + 10; // Set position depending on top or bottom if (direction === 'top') { droplet.style.left = `${xPos}px`; droplet.style.top = '0'; } else { droplet.style.left = `${xPos}px`; droplet.style.bottom = '0'; } container.appendChild(droplet); // Animate the droplet setTimeout(() => { droplet.style.transition = 'all 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94)'; droplet.style.opacity = '0.8'; droplet.style.transform = 'scale(1)'; if (direction === 'top') { droplet.style.top = `${Math.random() * 60 + 10}px`; } else { droplet.style.bottom = `${Math.random() * 60 + 10}px`; } // Create ripple effect setTimeout(() => { droplet.style.transform = 'scale(3)'; droplet.style.opacity = '0'; // Remove after animation setTimeout(() => { container.removeChild(droplet); }, 600); }, 600); }, i * 120); } // Show notification after droplets setTimeout(() => { showNotification(position, direction); }, 400); } // Show notification function showNotification(position, direction) { const notification = document.createElement('div'); notification.classList.add('water-notification'); if (direction === 'bottom') { notification.classList.add('bottom'); } // Get random notification data const notif = notificationData[Math.floor(Math.random() * notificationData.length)]; const message = notif.messages[Math.floor(Math.random() * notif.messages.length)]; // Create a timestamp const now = new Date(); const hours = now.getHours().toString().padStart(2, '0'); const minutes = now.getMinutes().toString().padStart(2, '0'); const timeString = `${hours}:${minutes}`; notification.innerHTML = ` <div class="notification-icon" style="background-color: ${notif.color}">${notif.icon}</div> <div class="notification-content"> <div class="notification-app">${notif.app}</div> <div class="notification-message">${message}</div> <div class="notification-time">Just now Β· ${timeString}</div> </div> <div class="notification-ripple"></div> `; const container = document.getElementById(`${direction}-container`); container.appendChild(notification); // Animate ripple const ripple = notification.querySelector('.notification-ripple'); ripple.style.animation = 'rippleAnimation 2s infinite'; // Add animation for the notification setTimeout(() => { notification.style.transition = 'all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1)'; notification.style.opacity = '1'; notification.style.transform = 'translateY(0)'; // Add keyframe animation for the ripple const style = document.createElement('style'); style.type = 'text/css'; style.innerHTML = ` @keyframes rippleAnimation { 0% { transform: translateX(-100%); } 100% { transform: translateX(100%); } } `; document.getElementsByTagName('head')[0].appendChild(style); // Auto dismiss after a delay setTimeout(() => { notification.style.opacity = '0'; notification.style.transform = direction === 'top' ? 'translateY(-100%)' : 'translateY(100%)'; // Remove from DOM after animation setTimeout(() => { container.removeChild(notification); }, 500); }, 5000); }, 100); } // Button event listeners document.getElementById('top-btn').addEventListener('click', function() { createWaterDroplet('top-right', 'top'); }); document.getElementById('bottom-btn').addEventListener('click', function() { createWaterDroplet('bottom-center', 'bottom'); }); </script> </body> </html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Water Ripple Tooltips</title> <style> :root { --primary-color: rgba(0, 140, 255, 0.8); --secondary-color: rgba(0, 200, 255, 0.4); --text-color: #2c3e50; --ripple-color: rgba(41, 128, 185, 0.15); --tooltip-bg: rgba(255, 255, 255, 0.95); --tooltip-shadow: rgba(100, 155, 237, 0.2); } * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { background: linear-gradient(135deg, #f5f7fa 0%, #e4f1fe 100%); height: 100vh; display: flex; flex-direction: column; justify-content: center; align-items: center; padding: 2rem; overflow: hidden; } .container { width: 100%; max-width: 700px; position: relative; padding: 2rem; border-radius: 16px; background-color: rgba(255, 255, 255, 0.7); backdrop-filter: blur(10px); box-shadow: 0 8px 32px rgba(31, 38, 135, 0.15); border: 1px solid rgba(255, 255, 255, 0.4); } h1 { color: var(--text-color); margin-bottom: 1.5rem; font-weight: 600; font-size: 2rem; text-align: center; background: linear-gradient(to right, #1a73e8, #8abbed); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; } p { color: var(--text-color); line-height: 1.6; margin-bottom: 2rem; font-size: 1rem; } .dashboard { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1.5rem; margin-bottom: 2rem; } .data-card { background-color: white; padding: 1.5rem; border-radius: 12px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05); transition: transform 0.3s ease, box-shadow 0.3s ease; cursor: pointer; position: relative; overflow: hidden; border: 1px solid rgba(0, 140, 255, 0.1); } .data-card:hover { transform: translateY(-5px); box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); } .data-card h3 { font-size: 1.2rem; margin-bottom: 0.75rem; color: var(--primary-color); } .data-card p { margin-bottom: 0; font-size: 0.9rem; color: #5d6778; } .data-value { font-size: 2rem; font-weight: 700; color: var(--text-color); margin-bottom: 0.75rem; } .feature-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1rem; } .feature-item { background-color: white; padding: 1.25rem; border-radius: 12px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05); position: relative; overflow: hidden; cursor: pointer; border: 1px solid rgba(0, 140, 255, 0.1); } .feature-icon { display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; background-color: var(--secondary-color); border-radius: 8px; margin-bottom: 1rem; } .feature-item h3 { font-size: 1.1rem; margin-bottom: 0.5rem; color: var(--primary-color); } .feature-item p { font-size: 0.9rem; margin-bottom: 0; color: #5d6778; } /* Tooltip styles */ .tooltip-wrapper { position: relative; } .tooltip { position: absolute; visibility: hidden; opacity: 0; background-color: var(--tooltip-bg); color: var(--text-color); padding: 1rem; border-radius: 8px; box-shadow: 0 10px 25px var(--tooltip-shadow); width: 250px; max-width: 100%; transform: translateY(10px); transition: opacity 0.3s, transform 0.3s; z-index: 100; border: 1px solid rgba(41, 128, 185, 0.2); overflow: hidden; } .tooltip::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: radial-gradient(circle at var(--x) var(--y), var(--ripple-color) 0%, transparent 50%); opacity: 0; z-index: -1; transform: scale(0); transition: transform 0.6s cubic-bezier(0.165, 0.84, 0.44, 1), opacity 0.6s cubic-bezier(0.165, 0.84, 0.44, 1); } .tooltip.active { visibility: visible; opacity: 1; transform: translateY(0); } .tooltip.ripple::before { opacity: 1; transform: scale(3); } .tooltip h4 { margin-bottom: 0.5rem; color: var(--primary-color); font-size: 1rem; } .tooltip p { font-size: 0.85rem; margin-bottom: 0.5rem; line-height: 1.4; } .tooltip-metric { display: flex; align-items: center; margin-top: 0.75rem; padding-top: 0.75rem; border-top: 1px solid rgba(0, 140, 255, 0.1); } .tooltip-metric span { font-size: 0.8rem; color: #5d6778; } .tooltip-metric strong { margin-left: 0.5rem; font-size: 0.8rem; color: var(--primary-color); } /* Wave animation on page load */ .wave { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at center, var(--ripple-color) 0%, transparent 70%); transform: scale(0); opacity: 0.8; border-radius: 50%; z-index: -1; animation: wave-animation 2s cubic-bezier(0.215, 0.610, 0.355, 1) forwards; } @keyframes wave-animation { 0% { transform: scale(0); opacity: 0.8; } 100% { transform: scale(4); opacity: 0; } } /* Responsive adjustments */ @media (max-width: 700px) { .container { padding: 1.5rem; } h1 { font-size: 1.5rem; } .dashboard, .feature-list { grid-template-columns: 1fr; } .tooltip { width: 200px; padding: 0.75rem; } } @media (max-width: 480px) { .container { padding: 1rem; } .tooltip { width: 180px; } } </style> </head> <body> <div class="container"> <h1>Interactive Water Analytics Dashboard</h1> <p>Hover over the cards below to explore detailed water consumption metrics with our new ripple effect tooltips. The visual feedback helps navigate complex data with ease.</p> <div class="dashboard"> <div class="data-card tooltip-wrapper" data-tooltip="household-usage"> <h3>Household Usage</h3> <div class="data-value">124<small>L</small></div> <p>Daily average consumption</p> <div class="tooltip" id="household-usage"> <h4>Daily Water Consumption</h4> <p>Your household uses 124 liters per day, which is 18% below the regional average of 151 liters.</p> <div class="tooltip-metric"> <span>Peak usage time:</span> <strong>6:30-8:00 AM</strong> </div> <div class="tooltip-metric"> <span>Conservation potential:</span> <strong>15L per day</strong> </div> </div> </div> <div class="data-card tooltip-wrapper" data-tooltip="monthly-trends"> <h3>Monthly Trends</h3> <div class="data-value">-8<small>%</small></div> <p>Reduction from last month</p> <div class="tooltip" id="monthly-trends"> <h4>Monthly Consumption Pattern</h4> <p>Your water usage decreased by 8% compared to last month, saving approximately 297 liters overall.</p> <div class="tooltip-metric"> <span>3-month trend:</span> <strong>Steadily decreasing</strong> </div> <div class="tooltip-metric"> <span>Projected savings:</span> <strong>$4.85 next bill</strong> </div> </div> </div> <div class="data-card tooltip-wrapper" data-tooltip="efficiency-score"> <h3>Efficiency Score</h3> <div class="data-value">87<small>/100</small></div> <p>Based on usage patterns</p> <div class="tooltip" id="efficiency-score"> <h4>Water Efficiency Rating</h4> <p>Your efficiency score of 87 puts you in the top 15% of water-conscious households in your area.</p> <div class="tooltip-metric"> <span>Improvement areas:</span> <strong>Shower duration, Irrigation</strong> </div> <div class="tooltip-metric"> <span>Next tier threshold:</span> <strong>90 points</strong> </div> </div> </div> </div> <div class="feature-list"> <div class="feature-item tooltip-wrapper" data-tooltip="smart-detection"> <div class="feature-icon">π§</div> <h3>Smart Leak Detection</h3> <p>Real-time monitoring of unusual water flow patterns</p> <div class="tooltip" id="smart-detection"> <h4>Smart Leak Detection System</h4> <p>Our AI algorithms analyze your water flow patterns 24/7 to identify potential leaks before they cause damage.</p> <div class="tooltip-metric"> <span>Detection accuracy:</span> <strong>98.3%</strong> </div> <div class="tooltip-metric"> <span>Average alert time:</span> <strong>< 3 minutes</strong> </div> </div> </div> <div class="feature-item tooltip-wrapper" data-tooltip="usage-insights"> <div class="feature-icon">π</div> <h3>Usage Insights</h3> <p>Detailed breakdown of consumption by appliance</p> <div class="tooltip" id="usage-insights"> <h4>Appliance-Level Insights</h4> <p>See exactly how each fixture and appliance contributes to your water footprint with our precise disaggregation technology.</p> <div class="tooltip-metric"> <span>Top consumer:</span> <strong>Shower (42%)</strong> </div> <div class="tooltip-metric"> <span>Most efficient:</span> <strong>Dishwasher (3.8L/cycle)</strong> </div> </div> </div> </div> <div class="wave"></div> </div> <script> document.addEventListener('DOMContentLoaded', function() { const tooltipWrappers = document.querySelectorAll('.tooltip-wrapper'); tooltipWrappers.forEach(wrapper => { const tooltip = wrapper.querySelector('.tooltip'); // Position tooltip based on wrapper dimensions and viewport function positionTooltip() { const wrapperRect = wrapper.getBoundingClientRect(); const tooltipRect = tooltip.getBoundingClientRect(); // Default position below the element let top = wrapperRect.height + 10; let left = (wrapperRect.width - tooltipRect.width) / 2; // Check if tooltip would go off right edge if (wrapperRect.left + left + tooltipRect.width > window.innerWidth) { left = window.innerWidth - tooltipRect.width - wrapperRect.left - 10; } // Check if tooltip would go off left edge if (wrapperRect.left + left < 0) { left = -wrapperRect.left + 10; } tooltip.style.top = `${top}px`; tooltip.style.left = `${left}px`; } // Show tooltip on hover wrapper.addEventListener('mouseenter', function(e) { positionTooltip(); tooltip.classList.add('active'); // Create initial ripple effect createRipple(e); }); // Continue ripple effect on mousemove within tooltip wrapper.addEventListener('mousemove', function(e) { if (tooltip.classList.contains('active')) { createRipple(e); } }); // Hide tooltip on mouseout wrapper.addEventListener('mouseleave', function() { tooltip.classList.remove('active', 'ripple'); }); // Create water ripple effect in tooltip function createRipple(e) { const rect = wrapper.getBoundingClientRect(); // Calculate relative position of mouse within the wrapper const x = e.clientX - rect.left; const y = e.clientY - rect.top; // Calculate percentage position for CSS variables const xPercent = (x / rect.width) * 100; const yPercent = (y / rect.height) * 100; // Set CSS variables for the ripple position tooltip.style.setProperty('--x', `${xPercent}%`); tooltip.style.setProperty('--y', `${yPercent}%`); // Remove and add ripple class to restart animation tooltip.classList.remove('ripple'); setTimeout(() => { tooltip.classList.add('ripple'); }, 10); } }); // Handle window resize for responsive tooltip positioning window.addEventListener('resize', function() { const activeTooltips = document.querySelectorAll('.tooltip.active'); activeTooltips.forEach(tooltip => { const wrapper = tooltip.closest('.tooltip-wrapper'); if (wrapper) { const wrapperRect = wrapper.getBoundingClientRect(); const tooltipRect = tooltip.getBoundingClientRect(); let top = wrapperRect.height + 10; let left = (wrapperRect.width - tooltipRect.width) / 2; if (wrapperRect.left + left + tooltipRect.width > window.innerWidth) { left = window.innerWidth - tooltipRect.width - wrapperRect.left - 10; } if (wrapperRect.left + left < 0) { left = -wrapperRect.left + 10; } tooltip.style.top = `${top}px`; tooltip.style.left = `${left}px`; } }); }); // Create multiple waves with different speeds for a more dynamic effect function createWaves() { const container = document.querySelector('.container'); const waveCount = 3; for (let i = 0; i < waveCount; i++) { setTimeout(() => { const wave = document.createElement('div'); wave.classList.add('wave'); container.appendChild(wave); // Remove wave after animation completes setTimeout(() => { wave.remove(); }, 2000); }, i * 500); } } // Initial wave effect createWaves(); }); </script> </body> </html>