<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
<!-- Ionicons -->
<script type="module" src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"></script>
<script nomodule src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"></script>
<body class="min-h-screen flex items-center justify-center p-0 md:p-8">
<main class="w-full">
<!-- Header context (optional) -->
<div class="text-center mb-8 opacity-0 animate-fade-in" style="animation: fadeIn 1s forwards;">
<p class="text-[#3CFFD5] uppercase tracking-widest text-xs font-semibold mb-2">Showcase</p>
<h1 class="text-3xl md:text-4xl font-light text-white">Featured Projects</h1>
</div>
<!-- Slider Component -->
<div class="slider-container" id="slider">
<div class="slider-track" id="track">
<!-- Slide 1 -->
<div class="slide active">
<img src="https://images.unsplash.com/photo-1493246507139-91e8fad9978e?q=80&w=2070&auto=format&fit=crop" alt="Alpine Lake">
<div class="slide-content">
<span class="tag">Nature</span>
<h2 class="text-2xl md:text-5xl">Alpine Serenity</h2>
<p class="text-sm md:text-lg mt-2">Experience the untouched beauty of the highlands, where the air is crisp and time stands still.</p>
</div>
</div>
<!-- Slide 2 -->
<div class="slide">
<img src="https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?q=80&w=2070&auto=format&fit=crop" alt="Modern Architecture">
<div class="slide-content">
<span class="tag">Architecture</span>
<h2 class="text-2xl md:text-5xl">Urban Geometry</h2>
<p class="text-sm md:text-lg mt-2">Exploring the intersection of light, shadow, and concrete in the heart of the metropolis.</p>
</div>
</div>
<!-- Slide 3 -->
<div class="slide">
<img src="https://images.unsplash.com/photo-1550684848-fac1c5b4e853?q=80&w=2070&auto=format&fit=crop" alt="Neon City">
<div class="slide-content">
<span class="tag">Nightlife</span>
<h2 class="text-2xl md:text-5xl">Neon Horizons</h2>
<p class="text-sm md:text-lg mt-2">The city wakes up when the sun goes down. A vibrant journey through cyberpunk aesthetics.</p>
</div>
</div>
<!-- Slide 4 -->
<div class="slide">
<img src="https://images.unsplash.com/photo-1469474968028-56623f02e42e?q=80&w=2074&auto=format&fit=crop" alt="Travel">
<div class="slide-content">
<span class="tag">Travel</span>
<h2 class="text-2xl md:text-5xl">Hidden Valleys</h2>
<p class="text-sm md:text-lg mt-2">Wandering off the beaten path to discover landscapes that defy imagination.</p>
</div>
</div>
</div>
<!-- Controls -->
<button class="nav-btn prev-btn" aria-label="Previous Slide">
<ion-icon name="chevron-back-outline" size="large"></ion-icon>
</button>
<button class="nav-btn next-btn" aria-label="Next Slide">
<ion-icon name="chevron-forward-outline" size="large"></ion-icon>
</button>
<!-- Indicators -->
<div class="indicators" id="indicators"></div>
</div>
</main>
:root {
--accent-color: #3CFFD5;
--text-dark: #0f172a;
--bg-dark: #0f1115;
--slide-duration: 0.8s;
--autoplay-duration: 5000ms;
}
body {
font-family: 'Inter', sans-serif;
background-color: var(--bg-dark);
color: white;
overflow-x: hidden;
}
/* Slider Container */
.slider-container {
position: relative;
width: 100%;
max-width: 1200px;
margin: 0 auto;
aspect-ratio: 16/9;
overflow: hidden;
border-radius: 16px;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
}
@media (max-width: 768px) {
.slider-container {
aspect-ratio: 4/3;
border-radius: 0;
}
}
/* Track */
.slider-track {
display: flex;
height: 100%;
transition: transform var(--slide-duration) cubic-bezier(0.25, 1, 0.5, 1);
will-change: transform;
}
/* Individual Slide */
.slide {
min-width: 100%;
height: 100%;
position: relative;
overflow: hidden;
}
.slide img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 6s ease; /* Subtle zoom effect */
}
.slide.active img {
transform: scale(1.05);
}
/* Overlay Gradient */
.slide::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(to top, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0.2) 50%, rgba(0,0,0,0) 100%);
z-index: 1;
}
/* Content */
.slide-content {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
padding: 3rem;
z-index: 2;
transform: translateY(20px);
opacity: 0;
transition: all 0.6s ease 0.3s;
}
.slide.active .slide-content {
transform: translateY(0);
opacity: 1;
}
/* Navigation Buttons */
.nav-btn {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 56px;
height: 56px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 10;
color: white;
transition: all 0.3s ease;
}
.nav-btn:hover {
background: var(--accent-color);
color: var(--text-dark);
box-shadow: 0 0 20px rgba(60, 255, 213, 0.4);
border-color: var(--accent-color);
}
.prev-btn { left: 2rem; }
.next-btn { right: 2rem; }
@media (max-width: 640px) {
.nav-btn { width: 40px; height: 40px; }
.prev-btn { left: 1rem; }
.next-btn { right: 1rem; }
.slide-content { padding: 1.5rem; }
}
/* Indicators / Dots */
.indicators {
position: absolute;
bottom: 2rem;
right: 3rem;
display: flex;
gap: 12px;
z-index: 10;
}
.dot {
width: 40px;
height: 4px;
background: rgba(255, 255, 255, 0.3);
border-radius: 2px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: background 0.3s;
}
.dot::after {
content: '';
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 0%;
background-color: var(--accent-color);
transition: width 0s linear;
}
/* Active State & Progress Animation */
.dot.active {
background: rgba(255, 255, 255, 0.1);
}
.dot.active::after {
width: 100%;
transition: width var(--autoplay-duration) linear;
}
/* For manual clicks, we remove transition briefly */
.dot.reset-animation::after {
width: 0%;
transition: none;
}
/* Typography */
h2 {
font-weight: 600;
letter-spacing: -0.02em;
margin-bottom: 0.5rem;
}
p {
color: rgba(255, 255, 255, 0.8);
font-weight: 300;
max-width: 600px;
}
/* Highlight Tag */
.tag {
display: inline-block;
padding: 4px 12px;
background: rgba(60, 255, 213, 0.15);
color: var(--accent-color);
border: 1px solid var(--accent-color);
border-radius: 100px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
margin-bottom: 1rem;
letter-spacing: 0.05em;
}
@keyframes fadeIn {
to { opacity: 1; }
}
.animate-fade-in {
animation-fill-mode: forwards;
}
document.addEventListener('DOMContentLoaded', () => {
const track = document.getElementById('track');
const slides = Array.from(track.children);
const nextBtn = document.querySelector('.next-btn');
const prevBtn = document.querySelector('.prev-btn');
const indicatorsContainer = document.getElementById('indicators');
const sliderContainer = document.getElementById('slider');
let currentIndex = 0;
let autoPlayInterval;
const autoPlayDelay = 5000; // 5 seconds matches CSS definition
let isPlaying = true;
// Create Indicators
slides.forEach((_, index) => {
const dot = document.createElement('div');
dot.classList.add('dot');
if (index === 0) dot.classList.add('active');
dot.addEventListener('click', () => {
gotoSlide(index);
resetTimer();
});
indicatorsContainer.appendChild(dot);
});
const dots = Array.from(indicatorsContainer.children);
// Core Slide Function
const updateSlidePosition = () => {
track.style.transform = `translateX(-${currentIndex * 100}%)`;
// Update Active Classes for CSS animations
slides.forEach(slide => slide.classList.remove('active'));
slides[currentIndex].classList.add('active');
// Update Dots & Progress Bar Animation
dots.forEach(dot => {
dot.classList.remove('active');
dot.classList.add('reset-animation'); // Reset CSS animation
});
// Force reflow to restart CSS animation
void dots[currentIndex].offsetWidth;
dots[currentIndex].classList.remove('reset-animation');
dots[currentIndex].classList.add('active');
};
const gotoSlide = (index) => {
if (index < 0) {
currentIndex = slides.length - 1;
} else if (index >= slides.length) {
currentIndex = 0;
} else {
currentIndex = index;
}
updateSlidePosition();
};
const nextSlide = () => {
gotoSlide(currentIndex + 1);
};
const prevSlide = () => {
gotoSlide(currentIndex - 1);
};
// Event Listeners
nextBtn.addEventListener('click', () => {
nextSlide();
resetTimer();
});
prevBtn.addEventListener('click', () => {
prevSlide();
resetTimer();
});
// Auto Play Logic
const startAutoPlay = () => {
// Ensure animation is running
dots[currentIndex].classList.remove('reset-animation');
dots[currentIndex].classList.add('active');
autoPlayInterval = setInterval(() => {
nextSlide();
}, autoPlayDelay);
};
const stopAutoPlay = () => {
clearInterval(autoPlayInterval);
// Pause visual indicator
dots[currentIndex].classList.add('reset-animation');
};
const resetTimer = () => {
stopAutoPlay();
if (isPlaying) startAutoPlay();
};
// Pause on Hover Interaction
sliderContainer.addEventListener('mouseenter', () => {
isPlaying = false;
stopAutoPlay();
});
sliderContainer.addEventListener('mouseleave', () => {
isPlaying = true;
startAutoPlay();
});
// Initialize
startAutoPlay();
// Touch Support (Swipe)
let touchStartX = 0;
let touchEndX = 0;
sliderContainer.addEventListener('touchstart', e => {
touchStartX = e.changedTouches[0].screenX;
stopAutoPlay();
}, {passive: true});
sliderContainer.addEventListener('touchend', e => {
touchEndX = e.changedTouches[0].screenX;
handleSwipe();
isPlaying = true;
startAutoPlay();
}, {passive: true});
const handleSwipe = () => {
if (touchEndX < touchStartX - 50) nextSlide();
if (touchEndX > touchStartX + 50) prevSlide();
}
});
No comments yet. Be the first!