Wave Motion Slider (CSS Wave)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nazmul // Wave Motion Slider</title>
<style>
:root {
--primary: #ffffff;
--accent: #ff3e00;
--bg: #0a0a0a;
--font-main: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
--transition-speed: 0.8s;
--wave-stagger: 0.025s;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: var(--bg);
color: var(--primary);
font-family: var(--font-main);
overflow: hidden;
height: 100vh;
}
/* --- Layout --- */
.app-container {
position: relative;
width: 100%;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.navbar {
position: absolute;
top: 0;
width: 100%;
padding: 40px;
display: flex;
justify-content: space-between;
align-items: center;
z-index: 100;
mix-blend-mode: difference;
}
.logo {
font-size: 1.2rem;
font-weight: 900;
letter-spacing: 5px;
text-transform: uppercase;
}
.nav-links {
display: flex;
gap: 40px;
list-style: none;
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 2px;
}
/* --- Slider Logic --- */
.slider {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
.slide {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
pointer-events: none;
transition: opacity 0.5s ease;
}
.slide.active {
opacity: 1;
pointer-events: all;
}
/* --- Content Styling --- */
.slide-content {
position: relative;
z-index: 10;
text-align: center;
max-width: 800px;
}
.slide-number {
font-size: 1rem;
display: block;
margin-bottom: 20px;
overflow: hidden;
}
.slide-number span {
display: block;
transform: translateY(100%);
transition: transform 0.8s cubic-bezier(0.16, 1, 0.3, 1);
}
.active .slide-number span {
transform: translateY(0);
}
.slide-title {
font-size: clamp(3rem, 10vw, 8rem);
font-weight: 800;
line-height: 0.9;
text-transform: uppercase;
margin-bottom: 30px;
overflow: hidden;
}
.slide-title span {
display: block;
transform: translateY(110%);
}
.active .slide-title span {
transform: translateY(0);
}
/* --- The Wave Transition Engine --- */
.wave-container {
position: absolute;
inset: 0;
display: flex;
z-index: 5;
pointer-events: none;
}
.wave-slat {
flex: 1;
height: 100%;
background-color: var(--accent);
transform: scaleY(0);
transform-origin: bottom;
}
.wave-slat.animate-up {
animation: waveUp var(--transition-speed) cubic-bezier(0.76, 0, 0.24, 1) forwards;
animation-delay: calc(var(--i) * var(--wave-stagger));
}
.wave-slat.animate-down {
animation: waveDown var(--transition-speed) cubic-bezier(0.76, 0, 0.24, 1) forwards;
animation-delay: calc(var(--i) * var(--wave-stagger));
transform-origin: top;
transform: scaleY(1);
}
@keyframes waveUp {
0% { transform: scaleY(0); }
100% { transform: scaleY(1); }
}
@keyframes waveDown {
0% { transform: scaleY(1); }
100% { transform: scaleY(0); }
}
/* --- Backgrounds --- */
.slide-bg {
position: absolute;
inset: 0;
background-size: cover;
background-position: center;
filter: brightness(0.4) grayscale(0.5);
transform: scale(1.1);
transition: transform 1.5s cubic-bezier(0.16, 1, 0.3, 1);
}
.active .slide-bg {
transform: scale(1);
}
/* --- Controls --- */
.controls {
position: absolute;
bottom: 60px;
right: 60px;
display: flex;
gap: 20px;
z-index: 100;
}
.btn {
background: none;
border: 1px solid rgba(255,255,255,0.3);
color: white;
width: 60px;
height: 60px;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.btn:hover {
border-color: var(--accent);
background: var(--accent);
}
.btn svg {
width: 20px;
height: 20px;
fill: currentColor;
}
/* --- Custom Progress Indicator --- */
.progress-wrap {
position: absolute;
bottom: 60px;
left: 60px;
z-index: 100;
display: flex;
align-items: center;
gap: 20px;
}
.line-outer {
width: 200px;
height: 2px;
background: rgba(255,255,255,0.1);
position: relative;
}
.line-inner {
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 0%;
background: var(--accent);
transition: width 0.5s ease;
}
</style>
</head>
<body>
<nav class="navbar">
<div class="logo">Nazmul Hub</div>
<ul class="nav-links">
<li>Projects</li>
<li>Studio</li>
<li>Contact</li>
</ul>
</nav>
<div class="app-container">
<!-- The Wave Effect Layer -->
<div id="wave-engine" class="wave-container"></div>
<div class="slider" id="main-slider">
<!-- Slide 1 -->
<div class="slide active" data-index="0" data-color="#ff3e00">
<div class="slide-bg" style="background-image: url('https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?auto=format&fit=crop&q=80&w=2070');"></div>
<div class="slide-content">
<span class="slide-number"><span>01 / 03</span></span>
<h1 class="slide-title"><span>Vertical<br>Horizon</span></h1>
</div>
</div>
<!-- Slide 2 -->
<div class="slide" data-index="1" data-color="#0066ff">
<div class="slide-bg" style="background-image: url('https://images.unsplash.com/photo-1503387762-592dee58c460?auto=format&fit=crop&q=80&w=2070');"></div>
<div class="slide-content">
<span class="slide-number"><span>02 / 03</span></span>
<h1 class="slide-title"><span>Brutalist<br>Echo</span></h1>
</div>
</div>
<!-- Slide 3 -->
<div class="slide" data-index="2" data-color="#00ff95">
<div class="slide-bg" style="background-image: url('https://images.unsplash.com/photo-1494438639946-1ebd1d20bf85?auto=format&fit=crop&q=80&w=2067');"></div>
<div class="slide-content">
<span class="slide-number"><span>03 / 03</span></span>
<h1 class="slide-title"><span>Nordic<br>Void</span></h1>
</div>
</div>
</div>
<div class="progress-wrap">
<span id="current-idx">01</span>
<div class="line-outer"><div class="line-inner" id="progress-bar"></div></div>
<span>03</span>
</div>
<div class="controls">
<button class="btn" id="prev-btn">
<svg viewBox="0 0 24 24"><path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/></svg>
</button>
<button class="btn" id="next-btn">
<svg viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
</button>
</div>
</div>
<script>
class WaveSlider {
constructor() {
this.slider = document.getElementById('main-slider');
this.slides = document.querySelectorAll('.slide');
this.waveEngine = document.getElementById('wave-engine');
this.nextBtn = document.getElementById('next-btn');
this.prevBtn = document.getElementById('prev-btn');
this.progressBar = document.getElementById('progress-bar');
this.currentIdxEl = document.getElementById('current-idx');
this.currentIndex = 0;
this.isAnimating = false;
this.slatCount = 40; // Number of vertical wave elements
this.init();
}
init() {
// Create the wave slats
for (let i = 0; i < this.slatCount; i++) {
const slat = document.createElement('div');
slat.classList.add('wave-slat');
slat.style.setProperty('--i', i);
this.waveEngine.appendChild(slat);
}
this.updateUI();
this.nextBtn.addEventListener('click', () => this.changeSlide(1));
this.prevBtn.addEventListener('click', () => this.changeSlide(-1));
// Keyboard support
window.addEventListener('keydown', (e) => {
if (e.key === 'ArrowRight') this.changeSlide(1);
if (e.key === 'ArrowLeft') this.changeSlide(-1);
});
}
updateUI() {
const progress = ((this.currentIndex + 1) / this.slides.length) * 100;
this.progressBar.style.width = `${progress}%`;
this.currentIdxEl.innerText = `0${this.currentIndex + 1}`;
// Update accent color based on slide data
const color = this.slides[this.currentIndex].dataset.color;
document.documentElement.style.setProperty('--accent', color);
}
async changeSlide(direction) {
if (this.isAnimating) return;
this.isAnimating = true;
const nextIndex = (this.currentIndex + direction + this.slides.length) % this.slides.length;
const slats = document.querySelectorAll('.wave-slat');
// Phase 1: Wave Covers Viewport
slats.forEach(slat => {
slat.classList.remove('animate-down');
slat.classList.add('animate-up');
slat.style.transformOrigin = 'bottom';
});
// Wait for the middle of the wave to peak
await new Promise(resolve => setTimeout(resolve, 800));
// Phase 2: Switch Slide Content
this.slides[this.currentIndex].classList.remove('active');
this.currentIndex = nextIndex;
this.slides[this.currentIndex].classList.add('active');
this.updateUI();
// Phase 3: Wave Recedes
slats.forEach(slat => {
slat.classList.remove('animate-up');
slat.classList.add('animate-down');
slat.style.transformOrigin = 'top';
});
// Finalize
setTimeout(() => {
this.isAnimating = false;
}, 800 + (this.slatCount * 25)); // Buffer for stagger
}
}
// Initialize production engine
document.addEventListener('DOMContentLoaded', () => {
new WaveSlider();
});
</script>
</body>
</html>