Custom Code Experiment •
A Carousel
Source Code View HTML / CSS / JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Animated Carousel</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--dark-bg: black;
--darker-bg: black;
--accent-purple: #8a2be2;
--accent-cyan: #00ffff;
--accent-pink: #ff00ff;
--text-color: #f0f0f0;
--card-width: 10rem;
--transition-speed: 0.7s;
}
html, body {
width: 100%;
height: 100%;
overflow: hidden;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
display: flex;
justify-content: center;
align-items: center;
background: radial-gradient(circle at center, var(--darker-bg), var(--dark-bg));
color: var(--text-color);
position: relative;
min-height: 100vh;
}
/* Animated background elements */
.bg-particle {
position: absolute;
border-radius: 50%;
background: rgba(138, 43, 226, 0.1);
z-index: -1;
animation: float 15s infinite linear;
}
@keyframes float {
0% { transform: translate(0, 0) rotate(0deg); }
100% { transform: translate(100px, 100px) rotate(360deg); }
}
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
max-width: 1200px;
margin: 0 auto;
padding: 0;
height: 100%;
}
.title {
text-align: center;
font-size: 2.5rem;
font-weight: 800;
background: linear-gradient(90deg, var(--accent-cyan), var(--accent-pink));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
text-shadow: 0 0 30px rgba(138, 43, 226, 0.3);
margin-bottom: 1rem;
z-index: 10;
}
.slider-wrapper {
display: flex;
align-items: center;
justify-content: center;
gap: 4rem;
padding: 0;
position: relative;
width: 100%;
}
.slider {
--slide-width: var(--card-width);
position: relative;
display: flex;
justify-content: center;
align-items: center;
gap: 0.5rem;
height: calc(var(--card-width) * 1.25);
width: calc(var(--card-width) * 5 + 2rem);
margin: 0 auto;
}
.slide {
--card-opacity: 0.8;
position: absolute;
width: var(--slide-width);
aspect-ratio: 4 / 5;
border-radius: 1rem;
opacity: 0.7;
backdrop-filter: blur(5px);
mix-blend-mode: normal;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
overflow: hidden;
transition: all var(--transition-speed) cubic-bezier(0.4, 0, 0.2, 1);
transform-origin: center;
top: 50%;
transform: translateY(-50%);
}
.slide::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 1rem;
padding: 3px;
background: linear-gradient(45deg, var(--accent-cyan), var(--accent-purple), var(--accent-pink));
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
opacity: 0;
transition: opacity 0.5s ease;
}
.slide:hover::before {
opacity: 1;
animation: borderGlow 2s infinite alternate;
}
@keyframes borderGlow {
0% {
background: linear-gradient(45deg, var(--accent-cyan), var(--accent-purple), var(--accent-pink));
}
100% {
background: linear-gradient(45deg, var(--accent-pink), var(--accent-cyan), var(--accent-purple));
}
}
/* Position slides based on their data-type */
.slide[data-type='active'] {
opacity: 1;
z-index: 10;
transform: translateY(-50%) scale(1.05);
animation: pulseGlow 3s infinite alternate;
}
@keyframes pulseGlow {
0% {
box-shadow: 0 0 20px rgba(138, 43, 226, 0.5);
}
100% {
box-shadow: 0 0 40px rgba(0, 255, 255, 0.7), 0 0 60px rgba(138, 43, 226, 0.4);
}
}
.slide[data-type='before'] {
opacity: 0.7;
z-index: 1;
transform: translateX(calc(-1 * (var(--card-width) + 0.5rem + 7rem))) translateY(-50%) scale(0.8);
}
.slide[data-type='after'] {
opacity: 0.7;
z-index: 1;
transform: translateX(calc(var(--card-width) + 0.5rem + 7rem)) translateY(-50%) scale(0.8);
}
.slide[data-type='hidden'] {
opacity: 0;
z-index: 0;
transform: translateX(0) translateY(-50%) scale(0.8);
}
/* Slide content styling */
.slide-content {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 1rem;
text-align: center;
position: relative;
z-index: 1;
}
.slide-number {
font-size: 3rem;
font-weight: 900;
margin-bottom: 0.5rem;
text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
}
.slide-title {
font-size: 0.9rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 0.5rem;
}
.slide-desc {
font-size: 0.7rem;
opacity: 0.9;
}
/* Slide-specific colors with dark mode adjustments */
.num1 { background: linear-gradient(135deg, #6b21a8, #9333ea); }
.num2 { background: linear-gradient(135deg, #0d9488, #14b8a6); }
.num3 { background: linear-gradient(135deg, #4338ca, #6366f1); }
.num4 { background: linear-gradient(135deg, #ca8a04, #eab308); }
.num5 { background: linear-gradient(135deg, #dc2626, #ef4444); }
.num6 { background: linear-gradient(135deg, #db2777, #ec4899); }
.num7 { background: linear-gradient(135deg, #7c3aed, #8b5cf6); }
.num8 { background: linear-gradient(135deg, #ea580c, #f97316); }
/* Navigation buttons */
[data-btn] {
cursor: pointer;
width: 4rem;
height: 4rem;
border-radius: 50%;
color: white;
border: 2px solid rgba(138, 43, 226, 0.7);
background: rgba(15, 15, 35, 0.8);
transition: all 0.3s ease;
font-size: 1.5rem;
font-weight: bold;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
z-index: 100;
flex-shrink: 0;
}
[data-btn]:hover {
transform: scale(1.1);
border-color: var(--accent-cyan);
background: rgba(138, 43, 226, 0.2);
box-shadow: 0 0 20px rgba(0, 255, 255, 0.5);
}
[data-btn]:active {
transform: scale(0.95);
}
[data-btn]:disabled {
opacity: 0.5;
cursor: not-allowed;
}
[data-btn]:disabled:hover {
transform: scale(1);
border-color: rgba(138, 43, 226, 0.7);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}
/* Controls info */
.controls-info {
text-align: center;
width: 100%;
font-size: 0.9rem;
opacity: 0.8;
margin-top: 1rem;
z-index: 10;
}
/* Animation classes */
.animate-next, .animate-prev {
transition: all var(--transition-speed) cubic-bezier(0.4, 0, 0.2, 1);
}
/* Responsive adjustments */
@media (max-width: 768px) {
.slider-wrapper {
gap: 2rem;
}
.slide {
--slide-width: 8rem;
}
.title {
font-size: 1.8rem;
margin-bottom: 0.5rem;
}
[data-btn] {
width: 3rem;
height: 3rem;
font-size: 1.2rem;
}
}
/* Centering fixes for iframe */
@media (max-height: 700px) {
.title {
font-size: 2rem;
margin-bottom: 0.5rem;
}
.slider-wrapper {
gap: 3rem;
}
.slide {
--slide-width: 9rem;
}
}
@media (max-height: 600px) {
.title {
font-size: 1.8rem;
margin-bottom: 0.25rem;
}
.slider-wrapper {
gap: 2rem;
}
.slide {
--slide-width: 8rem;
}
.controls-info {
margin-top: 0.5rem;
font-size: 0.8rem;
}
}
@media (max-height: 500px) {
.title {
font-size: 1.5rem;
margin-bottom: 0;
}
.slider-wrapper {
gap: 1.5rem;
}
.slide {
--slide-width: 7rem;
}
.controls-info {
margin-top: 0.25rem;
font-size: 0.7rem;
}
}
</style>
</head>
<body>
<div class="container">
<div class="slider-wrapper">
<button data-btn='previous' aria-label="Previous slide"><</button>
<div class="slider">
<!-- Slides will be dynamically positioned by JavaScript -->
</div>
<button data-btn='next' aria-label="Next slide">></button>
</div>
</div>
<script>
function createParticles() {
const particlesContainer = document.body;
const particleCount = 12;
for (let i = 0; i < particleCount; i++) {
const particle = document.createElement('div');
particle.classList.add('bg-particle');
const size = Math.random() * 60 + 15;
const posX = Math.random() * 100;
const posY = Math.random() * 100;
const duration = Math.random() * 30 + 20;
const delay = Math.random() * 5;
particle.style.width = `${size}px`;
particle.style.height = `${size}px`;
particle.style.left = `${posX}vw`;
particle.style.top = `${posY}vh`;
particle.style.animationDuration = `${duration}s`;
particle.style.animationDelay = `${delay}s`;
const colors = [
'rgba(138, 43, 226, 0.1)',
'rgba(0, 255, 255, 0.1)',
'rgba(255, 0, 255, 0.1)'
];
particle.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)];
particlesContainer.appendChild(particle);
}
}
class Carousel {
constructor() {
this.slides = [
{ number: 1, title: 'Purple Dream', desc: 'Ethereal and mystical', colorClass: 'num1' },
{ number: 2, title: 'Teal Depth', desc: 'Deep ocean vibes', colorClass: 'num2' },
{ number: 3, title: 'Royal Blue', desc: 'Majestic and bold', colorClass: 'num3' },
{ number: 4, title: 'Golden Sun', desc: 'Warm and radiant', colorClass: 'num4' },
{ number: 5, title: 'Crimson Fire', desc: 'Intense and passionate', colorClass: 'num5' },
{ number: 6, title: 'Pink Bloom', desc: 'Soft and delicate', colorClass: 'num6' },
{ number: 7, title: 'Violet Night', desc: 'Mysterious twilight', colorClass: 'num7' },
{ number: 8, title: 'Sunset Glow', desc: 'Warm evening hues', colorClass: 'num8' }
];
this.currentIndex = 1;
this.isAnimating = false;
this.container = document.querySelector('.slider');
this.prevBtn = document.querySelector('[data-btn="previous"]');
this.nextBtn = document.querySelector('[data-btn="next"]');
this.init();
}
init() {
this.renderSlides();
this.addEventListeners();
this.updateButtonStates();
this.centerCarousel();
}
renderSlides() {
this.container.innerHTML = '';
const totalSlides = this.slides.length;
for (let i = 0; i < totalSlides; i++) {
const slideData = this.slides[i];
const slideElement = document.createElement('div');
slideElement.className = `slide ${slideData.colorClass}`;
slideElement.dataset.index = i;
const diff = i - this.currentIndex;
if (diff === 0) {
slideElement.dataset.type = 'active';
} else if (diff === -1) {
slideElement.dataset.type = 'before';
} else if (diff === 1) {
slideElement.dataset.type = 'after';
} else if (diff === -2 || diff === totalSlides - 2) {
slideElement.dataset.type = 'hidden';
} else if (diff === 2 || diff === -totalSlides + 2) {
slideElement.dataset.type = 'hidden';
} else {
slideElement.dataset.type = 'hidden';
}
slideElement.innerHTML = `
<div class="slide-content">
<div class="slide-number">${slideData.number}</div>
<div class="slide-title">${slideData.title}</div>
<div class="slide-desc">${slideData.desc}</div>
</div>
`;
this.container.appendChild(slideElement);
}
}
centerCarousel() {
const container = document.querySelector('.container');
const title = document.querySelector('.title');
const sliderWrapper = document.querySelector('.slider-wrapper');
const controlsInfo = document.querySelector('.controls-info');
const totalHeight =
title.offsetHeight +
sliderWrapper.offsetHeight +
controlsInfo.offsetHeight;
const iframeHeight = window.innerHeight;
if (totalHeight < iframeHeight) {
const extraSpace = iframeHeight - totalHeight;
container.style.paddingTop = `${extraSpace / 3}px`;
container.style.paddingBottom = `${extraSpace / 3}px`;
} else {
container.style.paddingTop = '0';
container.style.paddingBottom = '0';
}
}
addEventListeners() {
this.prevBtn.addEventListener('click', () => this.previous());
this.nextBtn.addEventListener('click', () => this.next());
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowLeft') {
this.previous();
} else if (e.key === 'ArrowRight') {
this.next();
}
});
window.addEventListener('resize', () => this.centerCarousel());
}
updateButtonStates() {
this.prevBtn.disabled = this.isAnimating;
this.nextBtn.disabled = this.isAnimating;
}
previous() {
if (this.isAnimating) return;
this.isAnimating = true;
this.updateButtonStates();
const slides = this.container.querySelectorAll('.slide');
slides.forEach(slide => {
const currentType = slide.dataset.type;
if (currentType === 'active') {
slide.dataset.type = 'after';
slide.classList.add('animate-prev');
} else if (currentType === 'before') {
slide.dataset.type = 'hidden';
slide.classList.add('animate-prev');
} else if (currentType === 'after') {
const slideIndex = parseInt(slide.dataset.index);
const currentIndex = this.currentIndex;
const totalSlides = this.slides.length;
const expectedNextActive = (currentIndex - 1 + totalSlides) % totalSlides;
if (slideIndex === expectedNextActive) {
slide.dataset.type = 'active';
slide.classList.add('animate-prev');
} else {
slide.dataset.type = 'hidden';
slide.classList.add('animate-prev');
}
} else if (currentType === 'hidden') {
const slideIndex = parseInt(slide.dataset.index);
const currentIndex = this.currentIndex;
const totalSlides = this.slides.length;
const expectedNewBefore = (currentIndex - 2 + totalSlides) % totalSlides;
if (slideIndex === expectedNewBefore) {
slide.dataset.type = 'before';
slide.classList.add('animate-prev');
}
}
});
this.currentIndex = (this.currentIndex - 1 + this.slides.length) % this.slides.length;
setTimeout(() => {
slides.forEach(slide => {
slide.classList.remove('animate-prev');
});
this.isAnimating = false;
this.updateButtonStates();
this.renderSlides();
}, 700);
}
next() {
if (this.isAnimating) return;
this.isAnimating = true;
this.updateButtonStates();
const slides = this.container.querySelectorAll('.slide');
slides.forEach(slide => {
const currentType = slide.dataset.type;
if (currentType === 'active') {
slide.dataset.type = 'before';
slide.classList.add('animate-next');
} else if (currentType === 'after') {
slide.dataset.type = 'hidden';
slide.classList.add('animate-next');
} else if (currentType === 'before') {
const slideIndex = parseInt(slide.dataset.index);
const currentIndex = this.currentIndex;
const totalSlides = this.slides.length;
const expectedNextActive = (currentIndex + 1) % totalSlides;
if (slideIndex === expectedNextActive) {
slide.dataset.type = 'active';
slide.classList.add('animate-next');
} else {
slide.dataset.type = 'hidden';
slide.classList.add('animate-next');
}
} else if (currentType === 'hidden') {
const slideIndex = parseInt(slide.dataset.index);
const currentIndex = this.currentIndex;
const totalSlides = this.slides.length;
const expectedNewAfter = (currentIndex + 2) % totalSlides;
if (slideIndex === expectedNewAfter) {
slide.dataset.type = 'after';
slide.classList.add('animate-next');
}
}
});
this.currentIndex = (this.currentIndex + 1) % this.slides.length;
setTimeout(() => {
slides.forEach(slide => {
slide.classList.remove('animate-next');
});
this.isAnimating = false;
this.updateButtonStates();
this.renderSlides();
}, 700);
}
}
document.addEventListener('DOMContentLoaded', () => {
createParticles();
const carousel = new Carousel();
setTimeout(() => {
carousel.centerCarousel();
}, 100);
});
</script>
</body>
</html>