1. Project Overview & Features
The 3D Cosmic Memory Carousel & Flip Card Animation is an essential frontend project designed to help you bridge the gap between learning JavaScript syntax and building interactive, responsive web applications.
Key Features Implemented:
- 6 High-resolution holographic memory cards arranged in a 3D cylindrical carousel ring
- Interactive 3D card flip (rotateY 180deg) revealing coding origin stories and technical coordinates
- Smooth mouse drag & touch swipe rotation with velocity physics and snap-to-nearest-card damping
- Auto-play rotation mode with configurable speed controls (Slow, Normal, Fast)
- Live 3D spatial parameter sliders for Carousel Radius (250px-550px) and Perspective Depth (600px-1500px)
- Cosmic starfield drift and animated nebula background with glitch effect overlays
- Web Audio API sound synthesizers for carousel rotation whooshes and card flip chimes
- Celebration mode with floating 3D hearts, birthday cakes, and stars ❤️🎂✨
2. Step-by-Step Code Walkthrough
A Step 1: Semantic HTML Structure
We structure the interface using clean, semantic HTML elements. This ensures maximum accessibility and provides intuitive hooks for CSS classes and JavaScript query selectors.
<div class="cosmic-carousel-app" id="carouselApp">
<!-- Dynamic Cosmos & Starfield Background -->
<div class="cosmos-background">
<div class="stars-layer-1"></div>
<div class="stars-layer-2"></div>
<div class="nebula-glow"></div>
</div>
<!-- Confetti Canvas for Celebration -->
<canvas id="celebrationCanvas" class="celebration-canvas"></canvas>
<!-- Top App Header -->
<header class="app-header">
<div class="header-left">
<div class="badge-pill"><i class="fas fa-atom text-cyan"></i> 3D Spatial CSS Engine</div>
<h1 class="header-title">3D Cosmic Memory Carousel</h1>
</div>
<div class="header-right">
<button type="button" class="dock-btn" id="btnAudioToggle" title="Toggle Synth Audio">
<i class="fas fa-volume-up"></i>
</button>
<button type="button" class="dock-btn" id="btnThemeToggle" title="Switch Color Theme">
<i class="fas fa-palette"></i>
</button>
<button type="button" class="dock-btn" id="btnAutoPlayToggle" title="Toggle Auto-Play Rotation">
<i class="fas fa-play" id="autoPlayIcon"></i>
</button>
<button type="button" class="dock-btn btn-celebrate" id="btnCelebrate" title="Trigger Celebration Burst">
<i class="fas fa-heart text-pink"></i>
</button>
</div>
</header>
<!-- Main 3D Carousel Stage Viewport -->
<main class="carousel-viewport">
<div class="carousel-container" id="carouselContainer">
<div class="carousel" id="memoryCarousel">
<!-- Card 1: The Genesis Line -->
<div class="memory-card" data-index="0">
<div class="card-inner">
<div class="card-front">
<div class="card-content">
<div class="memory-tag tag-purple"><i class="fas fa-terminal me-1"></i> LANGUAGE: TypeScript & Python</div>
<h3 class="card-title">The Genesis Line</h3>
<div class="memory-image img-purple">
<i class="fas fa-code fa-3x icon-glow"></i>
<div class="glitch-effect"></div>
</div>
<p class="memory-preview">
The terminal glowed in the quiet dark as my first ‘Hello, World!’ sparked to life...
</p>
<div class="card-hint"><i class="fas fa-rotate me-1"></i> Click to flip card</div>
<div class="card-glow"></div>
</div>
</div>
<div class="card-back">
<div class="card-content">
<div class="memory-tag tag-purple">ORIGIN STORY</div>
<h3 class="card-title">The Genesis Line</h3>
<p class="card-story">
A blank file, a blinking cursor, and infinite possibilities. That first line of code wasn't just text—it was an invitation to build whole worlds from pure thought and logic.
</p>
<div class="memory-coordinates">
<span><i class="fas fa-location-dot me-1"></i> console: ~/genesis</span>
<span class="time-stamp"><i class="far fa-clock me-1"></i> 00:00:01 UTC</span>
</div>
</div>
</div>
</div>
</div>
<!-- Card 2: The Flow State Matrix -->
<div class="memory-card" data-index="1">
<div class="card-inner">
<div class="card-front">
<div class="card-content">
<div class="memory-tag tag-cyan"><i class="fas fa-bolt me-1"></i> FRAMEWORK: Next.js & Vue</div>
<h3 class="card-title">The Flow Matrix</h3>
<div class="memory-image img-cyan">
<i class="fas fa-bolt-lightning fa-3x icon-glow"></i>
<div class="glitch-effect"></div>
</div>
<p class="memory-preview">
Hours dissolved into seconds as reactive components clicked together in seamless harmony...
</p>
<div class="card-hint"><i class="fas fa-rotate me-1"></i> Click to flip card</div>
<div class="card-glow"></div>
</div>
</div>
<div class="card-back">
<div class="card-content">
<div class="memory-tag tag-cyan">DEEP FOCUS</div>
<h3 class="card-title">The Flow Matrix</h3>
<p class="card-story">
When logic flows faster than fingers can type. State synchronized, reactivity ignited, and complex abstractions felt as natural as breathing. Pure creative momentum in the flow.
</p>
<div class="memory-coordinates">
<span><i class="fas fa-location-dot me-1"></i> localhost:3000/flow</span>
<span class="time-stamp"><i class="far fa-clock me-1"></i> 03:45:12 AM</span>
</div>
</div>
</div>
</div>
</div>
<!-- Card 3: The Quantum Library -->
<div class="memory-card" data-index="2">
<div class="card-inner">
<div class="card-front">
<div class="card-content">
<div class="memory-tag tag-pink"><i class="fas fa-brain me-1"></i> CONCEPT: Graph Theory & AI</div>
<h3 class="card-title">Quantum Library</h3>
<div class="memory-image img-pink">
<i class="fas fa-book-open fa-3x icon-glow"></i>
<div class="glitch-effect"></div>
</div>
<p class="memory-preview">
Traversing multidimensional trees and neural networks in search of optimal paths...
</p>
<div class="card-hint"><i class="fas fa-rotate me-1"></i> Click to flip card</div>
<div class="card-glow"></div>
</div>
</div>
<div class="card-back">
<div class="card-content">
<div class="memory-tag tag-pink">ALGORITHMS</div>
<h3 class="card-title">Quantum Library</h3>
<p class="card-story">
Searching for the most elegant path through infinite nodes. Every algorithmic optimization pruned away milliseconds of latency until calculations felt instantaneous.
</p>
<div class="memory-coordinates">
<span><i class="fas fa-location-dot me-1"></i> arXiv:2609.1102</span>
<span class="time-stamp"><i class="far fa-clock me-1"></i> 11:20:00 EST</span>
</div>
</div>
</div>
</div>
</div>
<!-- Card 4: The Abstract Void -->
<div class="memory-card" data-index="3">
<div class="card-inner">
<div class="card-front">
<div class="card-content">
<div class="memory-tag tag-emerald"><i class="fas fa-cubes me-1"></i> PARADIGM: 3D WebGL Shaders</div>
<h3 class="card-title">The Abstract Void</h3>
<div class="memory-image img-emerald">
<i class="fas fa-atom fa-3x icon-glow"></i>
<div class="glitch-effect"></div>
</div>
<p class="memory-preview">
Manipulating vertices in non-Euclidean space with GLSL fragment shaders and photons...
</p>
<div class="card-hint"><i class="fas fa-rotate me-1"></i> Click to flip card</div>
<div class="card-glow"></div>
</div>
</div>
<div class="card-back">
<div class="card-content">
<div class="memory-tag tag-emerald">GRAPHICS</div>
<h3 class="card-title">The Abstract Void</h3>
<p class="card-story">
Where mathematics transforms into light and geometry. Raymarching through signed distance fields, refracting ambient photons across digital glass surfaces in real time.
</p>
<div class="memory-coordinates">
<span><i class="fas fa-location-dot me-1"></i> shader: fragment.glsl</span>
<span class="time-stamp"><i class="far fa-clock me-1"></i> 18:30:45 UTC</span>
</div>
</div>
</div>
</div>
</div>
<!-- Card 5: The Version Mirror -->
<div class="memory-card" data-index="4">
<div class="card-inner">
<div class="card-front">
<div class="card-content">
<div class="memory-tag tag-amber"><i class="fas fa-code-branch me-1"></i> TOOL: Git Branching</div>
<h3 class="card-title">The Version Mirror</h3>
<div class="memory-image img-amber">
<i class="fas fa-code-fork fa-3x icon-glow"></i>
<div class="glitch-effect"></div>
</div>
<p class="memory-preview">
Branching realities across parallel commits, rebasing history into unified harmony...
</p>
<div class="card-hint"><i class="fas fa-rotate me-1"></i> Click to flip card</div>
<div class="card-glow"></div>
</div>
</div>
<div class="card-back">
<div class="card-content">
<div class="memory-tag tag-amber">VERSION CONTROL</div>
<h3 class="card-title">The Version Mirror</h3>
<p class="card-story">
A multiverse of parallel ideas branching across commits. Resolving merge conflicts wasn't just fixing diffs—it was merging divergent timelines into a single elegant masterwork.
</p>
<div class="memory-coordinates">
<span><i class="fas fa-location-dot me-1"></i> github.com/universe</span>
<span class="time-stamp"><i class="far fa-clock me-1"></i> 21:14:00</span>
</div>
</div>
</div>
</div>
</div>
<!-- Card 6: The Production Dream -->
<div class="memory-card" data-index="5">
<div class="card-inner">
<div class="card-front">
<div class="card-content">
<div class="memory-tag tag-rose"><i class="fas fa-rocket me-1"></i> PROCESS: Cloud CI/CD</div>
<h3 class="card-title">Production Dream</h3>
<div class="memory-image img-rose">
<i class="fas fa-rocket fa-3x icon-glow"></i>
<div class="glitch-effect"></div>
</div>
<p class="memory-preview">
Deploying into the global edge network, watching all lights turn green across nodes...
</p>
<div class="card-hint"><i class="fas fa-rotate me-1"></i> Click to flip card</div>
<div class="card-glow"></div>
</div>
</div>
<div class="card-back">
<div class="card-content">
<div class="memory-tag tag-rose">SHIP IT</div>
<h3 class="card-title">Production Dream</h3>
<p class="card-story">
All pipelines green, containers initialized, edge networks warm. The deployment went live across 40 global regions in under 3 seconds. The universe received our code.
</p>
<div class="memory-coordinates">
<span><i class="fas fa-location-dot me-1"></i> edge-cluster-01.global</span>
<span class="time-stamp"><i class="far fa-clock me-1"></i> NOW</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Navigation Arrow Controls -->
<div class="carousel-controls">
<button type="button" id="prevBtn" class="control-btn" aria-label="Previous Memory Card">
<i class="fas fa-chevron-left"></i>
</button>
<div class="card-stepper-dots" id="cardDots"></div>
<button type="button" id="nextBtn" class="control-btn" aria-label="Next Memory Card">
<i class="fas fa-chevron-right"></i>
</button>
</div>
</main>
<!-- Bottom Inspector & 3D Parameters Dock -->
<div class="bottom-inspector-dock" id="bottomDock">
<div class="dock-header">
<div class="dock-tabs">
<button type="button" class="dock-tab active" data-tab="params"><i class="fas fa-sliders me-1"></i> 3D Parameters</button>
<button type="button" class="dock-tab" data-tab="code"><i class="fas fa-code me-1 text-cyan"></i> CSS 3D Math</button>
</div>
<div class="dock-actions">
<button type="button" class="btn-toggle-dock" id="btnToggleDock" title="Collapse / Expand Dock">
<i class="fas fa-chevron-down" id="dockChevron"></i>
</button>
</div>
</div>
<!-- Tab 1: 3D Spatial Parameters -->
<div class="tab-panel active" id="panelParams">
<div class="trig-controls-grid">
<div class="trig-control">
<div class="control-header">
<span class="ctrl-label">Carousel 3D Radius:</span>
<strong class="ctrl-val" id="valRadius">400px</strong>
</div>
<input type="range" class="range-slider" id="sliderRadius" min="250" max="550" value="400" step="10">
</div>
<div class="trig-control">
<div class="control-header">
<span class="ctrl-label">Perspective Depth:</span>
<strong class="ctrl-val" id="valPerspective">1000px</strong>
</div>
<input type="range" class="range-slider" id="sliderPerspective" min="600" max="1500" value="1000" step="50">
</div>
<div class="trig-control">
<div class="control-header">
<span class="ctrl-label">Auto-Rotation Speed:</span>
<strong class="ctrl-val" id="valSpeed">1.0x</strong>
</div>
<div class="preset-btn-group">
<button type="button" class="btn-preset active" data-speed="1.0">Normal</button>
<button type="button" class="btn-preset" data-speed="0.5">Slow</button>
<button type="button" class="btn-preset" data-speed="2.0">Fast</button>
</div>
</div>
</div>
</div>
<!-- Tab 2: CSS Math Snippet -->
<div class="tab-panel" id="panelCode">
<pre class="code-snippet"><code class="language-css">/* CYLINDRICAL 3D CAROUSEL POSITIONING */
.carousel {
perspective: 1000px;
transform-style: preserve-3d;
}
/* Card Angle = (360deg / totalCards) * index */
.memory-card {
transform: rotateY(calc(var(--i) * 60deg)) translateZ(400px);
transition: transform 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
/* 3D Flip Card */
.memory-card.flipped .card-inner {
transform: rotateY(180deg);
}</code></pre>
</div>
</div>
</div>B Step 2: Styling & Responsive CSS
Modern CSS flexbox and grid layouts are used to make the UI look polished, centered, and responsive across desktop, tablet, and mobile screens.
/* 3D Cosmic Memory Carousel Styling */
:root {
--primary: #a855f7;
--secondary: #06b6d4;
--accent: #f43f5e;
--background: #070714;
--card-bg: rgba(18, 18, 38, 0.78);
--card-border: rgba(168, 85, 247, 0.35);
--text-primary: #ffffff;
--text-secondary: #cbd5e1;
--glow-primary: 0 0 15px rgba(168, 85, 247, 0.65), 0 0 30px rgba(168, 85, 247, 0.35);
--glow-secondary: 0 0 15px rgba(6, 182, 212, 0.65), 0 0 30px rgba(6, 182, 212, 0.35);
--card-width: 290px;
--card-height: 410px;
--carousel-radius: 400px;
--perspective-depth: 1000px;
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}
/* Theme 2: Cyber Gold & Emerald */
.theme-gold {
--primary: #f59e0b;
--secondary: #10b981;
--accent: #ef4444;
--background: #090a0f;
--card-bg: rgba(28, 22, 12, 0.8);
--card-border: rgba(245, 158, 11, 0.35);
}
/* Theme 3: Sakura Blossom */
.theme-sakura {
--primary: #ec4899;
--secondary: #8b5cf6;
--accent: #fb7185;
--background: #140b24;
--card-bg: rgba(36, 16, 48, 0.8);
--card-border: rgba(236, 72, 153, 0.35);
}
/* Theme 4: Deep Ocean Hologram */
.theme-ocean {
--primary: #38bdf8;
--secondary: #2dd4bf;
--accent: #818cf8;
--background: #031024;
--card-bg: rgba(8, 28, 54, 0.8);
--card-border: rgba(56, 189, 248, 0.35);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cosmic-carousel-app {
position: relative;
width: 100%;
height: 100vh;
min-height: 640px;
background-color: var(--background);
font-family: var(--font-sans);
color: var(--text-primary);
overflow: hidden;
user-select: none;
display: flex;
flex-direction: column;
transition: background-color 0.5s ease;
}
/* Cosmic Starfield Background */
.cosmos-background {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
overflow: hidden;
pointer-events: none;
}
.stars-layer-1, .stars-layer-2 {
position: absolute;
width: 100%;
height: 100%;
background-image:
radial-gradient(1.5px 1.5px at 20% 30%, #ffffff, rgba(0,0,0,0)),
radial-gradient(1.5px 1.5px at 40% 70%, #ffffff, rgba(0,0,0,0)),
radial-gradient(2px 2px at 60% 20%, #a855f7, rgba(0,0,0,0)),
radial-gradient(1.5px 1.5px at 80% 80%, #06b6d4, rgba(0,0,0,0)),
radial-gradient(2px 2px at 90% 40%, #ffffff, rgba(0,0,0,0));
background-size: 320px 320px, 450px 450px, 500px 500px, 600px 600px, 700px 700px;
background-repeat: repeat;
animation: starsDrift 60s linear infinite;
}
.stars-layer-2 {
opacity: 0.6;
animation: starsDrift 120s linear infinite reverse;
}
@keyframes starsDrift {
0% { transform: translateY(0px); }
100% { transform: translateY(-600px); }
}
.nebula-glow {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 800px;
height: 800px;
background: radial-gradient(circle, rgba(168, 85, 247, 0.15) 0%, rgba(6, 182, 212, 0.08) 45%, transparent 70%);
filter: blur(50px);
pointer-events: none;
}
/* Confetti Celebration Canvas */
.celebration-canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 40;
pointer-events: none;
}
/* Top App Header */
.app-header {
position: absolute;
top: 18px;
left: 24px;
right: 24px;
display: flex;
justify-content: space-between;
align-items: center;
z-index: 30;
}
.header-left, .header-right {
display: flex;
align-items: center;
gap: 12px;
}
.badge-pill {
display: inline-flex;
align-items: center;
gap: 6px;
background: rgba(255, 255, 255, 0.08);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
padding: 5px 12px;
border-radius: 9999px;
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
color: #e2e8f0;
border: 1px solid rgba(255, 255, 255, 0.12);
}
.text-cyan { color: var(--secondary) !important; }
.text-pink { color: var(--accent) !important; }
.header-title {
font-size: 1.15rem;
font-weight: 800;
color: #ffffff;
letter-spacing: -0.02em;
background: linear-gradient(90deg, #ffffff 0%, #cbd5e1 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
/* Dock Buttons */
.dock-btn {
width: 38px;
height: 38px;
border-radius: 11px;
background: rgba(255, 255, 255, 0.08);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.15);
color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.92rem;
cursor: pointer;
transition: all 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.dock-btn:hover {
transform: translateY(-2px);
background: rgba(255, 255, 255, 0.18);
box-shadow: 0 0 16px rgba(168, 85, 247, 0.4);
}
.dock-btn:active {
transform: scale(0.94);
}
.btn-celebrate:hover {
box-shadow: 0 0 16px rgba(244, 63, 94, 0.6);
}
/* Main 3D Carousel Viewport */
.carousel-viewport {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
z-index: 10;
padding: 60px 20px 100px 20px;
}
/* 3D Carousel Stage */
.carousel-container {
position: relative;
width: 100%;
height: 460px;
perspective: var(--perspective-depth);
transform-style: preserve-3d;
display: flex;
justify-content: center;
align-items: center;
cursor: grab;
}
.carousel-container:active {
cursor: grabbing;
}
.carousel {
position: relative;
width: 0;
height: 0;
transform-style: preserve-3d;
transition: transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
}
/* Individual Memory Card (3D Cylinder Ring) */
.memory-card {
position: absolute;
width: var(--card-width);
height: var(--card-height);
left: 50%;
top: 50%;
margin-left: calc(var(--card-width) / -2);
margin-top: calc(var(--card-height) / -2);
transform-style: preserve-3d;
transition: transform 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275);
cursor: pointer;
}
.card-inner {
position: relative;
width: 100%;
height: 100%;
transform-style: preserve-3d;
transition: transform 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.memory-card:hover .card-inner {
transform: translateZ(28px);
}
.memory-card.flipped .card-inner {
transform: rotateY(180deg) translateZ(10px);
}
/* Card Front & Back Faces */
.card-front, .card-back {
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
border-radius: 20px;
overflow: hidden;
background: var(--card-bg);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid var(--card-border);
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.1);
transition: border-color 0.3s ease, box-shadow 0.3s ease;
}
.card-back {
transform: rotateY(180deg);
border-color: rgba(6, 182, 212, 0.4);
}
.card-content {
padding: 24px;
height: 100%;
display: flex;
flex-direction: column;
position: relative;
}
/* Tag Pill */
.memory-tag {
font-size: 0.72rem;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.06em;
margin-bottom: 12px;
display: inline-flex;
align-items: center;
}
.tag-purple { color: #c084fc; }
.tag-cyan { color: #38bdf8; }
.tag-pink { color: #fb7185; }
.tag-emerald { color: #34d399; }
.tag-amber { color: #fbbf24; }
.tag-rose { color: #f43f5e; }
.card-title {
font-size: 1.35rem;
font-weight: 800;
margin-bottom: 14px;
color: #ffffff;
letter-spacing: -0.01em;
}
/* Card Center Graphic Icon */
.memory-image {
width: 100%;
height: 140px;
margin-bottom: 16px;
display: flex;
justify-content: center;
align-items: center;
position: relative;
overflow: hidden;
border-radius: 14px;
background: rgba(0, 0, 0, 0.35);
border: 1px solid rgba(255, 255, 255, 0.08);
}
.img-purple .icon-glow { color: #c084fc; text-shadow: 0 0 20px rgba(192, 132, 252, 0.8); }
.img-cyan .icon-glow { color: #38bdf8; text-shadow: 0 0 20px rgba(56, 189, 248, 0.8); }
.img-pink .icon-glow { color: #fb7185; text-shadow: 0 0 20px rgba(251, 113, 133, 0.8); }
.img-emerald .icon-glow { color: #34d399; text-shadow: 0 0 20px rgba(52, 211, 153, 0.8); }
.img-amber .icon-glow { color: #fbbf24; text-shadow: 0 0 20px rgba(251, 191, 36, 0.8); }
.img-rose .icon-glow { color: #f43f5e; text-shadow: 0 0 20px rgba(244, 63, 94, 0.8); }
.glitch-effect {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(45deg, transparent 65%, rgba(255, 255, 255, 0.15) 70%, transparent 75%);
background-size: 200% 200%;
animation: glitch 4s linear infinite;
pointer-events: none;
}
@keyframes glitch {
0% { background-position: 0 0; }
100% { background-position: 200% 200%; }
}
.memory-preview {
font-size: 0.86rem;
color: var(--text-secondary);
line-height: 1.5;
margin-bottom: 12px;
flex-grow: 1;
}
.card-story {
font-size: 0.88rem;
color: var(--text-secondary);
line-height: 1.6;
margin-bottom: 18px;
flex-grow: 1;
}
.card-hint {
font-size: 0.74rem;
font-weight: 700;
color: rgba(255, 255, 255, 0.4);
text-align: right;
}
.memory-coordinates {
font-family: var(--font-mono);
font-size: 0.76rem;
color: var(--secondary);
display: flex;
justify-content: space-between;
align-items: center;
border-top: 1px solid rgba(255, 255, 255, 0.1);
padding-top: 12px;
}
.time-stamp {
color: var(--accent);
}
.card-glow {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 20px;
pointer-events: none;
background: radial-gradient(circle at 50% 50%, rgba(168, 85, 247, 0.18), transparent 70%);
opacity: 0;
transition: opacity 0.3s ease;
}
.memory-card:hover .card-glow {
opacity: 1;
}
/* Carousel Controls & Stepper Dots */
.carousel-controls {
margin-top: 18px;
display: flex;
align-items: center;
gap: 16px;
z-index: 25;
}
.control-btn {
background: rgba(255, 255, 255, 0.08);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.15);
color: #ffffff;
width: 44px;
height: 44px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.3);
}
.control-btn:hover {
background: rgba(255, 255, 255, 0.2);
transform: scale(1.12);
box-shadow: 0 0 18px rgba(168, 85, 247, 0.5);
}
.card-stepper-dots {
display: flex;
gap: 8px;
align-items: center;
}
.dot-indicator {
width: 8px;
height: 8px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.25);
cursor: pointer;
transition: all 0.3s ease;
}
.dot-indicator.active {
width: 24px;
border-radius: 9999px;
background: var(--primary);
box-shadow: 0 0 10px var(--primary);
}
/* Bottom Inspector Dock */
.bottom-inspector-dock {
position: absolute;
bottom: 14px;
left: 50%;
transform: translateX(-50%);
width: 90%;
max-width: 600px;
background: rgba(15, 23, 42, 0.85);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 18px;
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.5);
z-index: 30;
overflow: hidden;
transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.bottom-inspector-dock.collapsed {
max-height: 44px;
}
.bottom-inspector-dock.collapsed .tab-panel {
display: none !important;
}
.dock-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 10px;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(0, 0, 0, 0.25);
}
.dock-tabs {
display: flex;
gap: 4px;
}
.dock-tab {
background: transparent;
border: none;
outline: none;
padding: 5px 12px;
font-size: 0.78rem;
font-weight: 700;
color: #94a3b8;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
}
.dock-tab:hover {
color: #ffffff;
background: rgba(255, 255, 255, 0.08);
}
.dock-tab.active {
background: rgba(255, 255, 255, 0.15);
color: #ffffff;
}
.btn-toggle-dock {
background: transparent;
border: none;
width: 30px;
height: 30px;
border-radius: 6px;
color: #94a3b8;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.85rem;
}
.tab-panel {
display: none;
padding: 14px 18px;
}
.tab-panel.active {
display: block;
}
.trig-controls-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 14px;
}
.trig-control {
display: flex;
flex-direction: column;
gap: 4px;
}
.control-header {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.76rem;
}
.ctrl-label {
color: #94a3b8;
font-weight: 600;
}
.ctrl-val {
color: var(--primary);
font-weight: 800;
}
.range-slider {
width: 100%;
accent-color: var(--primary);
cursor: pointer;
}
.preset-btn-group {
display: flex;
gap: 4px;
}
.btn-preset {
flex: 1;
padding: 4px;
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 6px;
background: transparent;
font-size: 0.72rem;
font-weight: 700;
color: #94a3b8;
cursor: pointer;
}
.btn-preset.active {
background: var(--primary);
color: #ffffff;
border-color: var(--primary);
}
.code-snippet {
margin: 0;
padding: 10px 12px;
background: #090a0f;
border-radius: 10px;
color: #e2e8f0;
font-family: var(--font-mono);
font-size: 0.74rem;
line-height: 1.4;
overflow-x: auto;
max-height: 120px;
}
@media (max-width: 768px) {
:root {
--card-width: 240px;
--card-height: 350px;
--carousel-radius: 280px;
}
.app-header {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.carousel-container {
height: 380px;
}
.card-content {
padding: 16px;
}
.memory-image {
height: 100px;
}
.card-title {
font-size: 1.1rem;
}
.trig-controls-grid {
grid-template-columns: 1fr;
}
.bottom-inspector-dock {
width: 94%;
bottom: 6px;
}
}C Step 3: JavaScript Logic & Event Handling
Here is the complete JavaScript code handling the state, event listeners, mathematical logic, and dynamic DOM rendering:
(() => {
// DOM Elements
const app = document.getElementById('carouselApp');
const carouselContainer = document.getElementById('carouselContainer');
const carousel = document.getElementById('memoryCarousel');
const cards = document.querySelectorAll('.memory-card');
const prevBtn = document.getElementById('prevBtn');
const nextBtn = document.getElementById('nextBtn');
const cardDots = document.getElementById('cardDots');
const celebrationCanvas = document.getElementById('celebrationCanvas');
// Controls & Sliders
const btnAudioToggle = document.getElementById('btnAudioToggle');
const btnThemeToggle = document.getElementById('btnThemeToggle');
const btnAutoPlayToggle = document.getElementById('btnAutoPlayToggle');
const autoPlayIcon = document.getElementById('autoPlayIcon');
const btnCelebrate = document.getElementById('btnCelebrate');
const bottomDock = document.getElementById('bottomDock');
const btnToggleDock = document.getElementById('btnToggleDock');
const dockChevron = document.getElementById('dockChevron');
const dockTabs = document.querySelectorAll('.dock-tab');
const tabPanels = document.querySelectorAll('.tab-panel');
const sliderRadius = document.getElementById('sliderRadius');
const valRadius = document.getElementById('valRadius');
const sliderPerspective = document.getElementById('sliderPerspective');
const valPerspective = document.getElementById('valPerspective');
const presetBtns = document.querySelectorAll('.btn-preset');
// State
let currentIndex = 0;
let theta = 0;
let radius = window.innerWidth <= 768 ? 280 : 400;
const totalCards = cards.length;
const anglePerCard = 360 / totalCards;
let isDragging = false;
let startX = 0;
let currentDragTheta = 0;
let autoPlay = false;
let autoPlayTimer = null;
let autoPlaySpeed = 1.0;
let audioEnabled = true;
let audioCtx = null;
let themeIndex = 0;
const themeClasses = ['', 'theme-gold', 'theme-sakura', 'theme-ocean'];
// --- 1. Web Audio Synthesizer ---
function initAudio() {
if (!audioCtx) {
const AudioContext = window.AudioContext || window.webkitAudioContext;
audioCtx = new AudioContext();
}
if (audioCtx.state === 'suspended') {
audioCtx.resume();
}
}
function playTone(freq, dur, type = 'triangle', vol = 0.2) {
if (!audioEnabled) return;
initAudio();
if (!audioCtx) return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = type;
osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
gain.gain.setValueAtTime(0.001, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(vol, audioCtx.currentTime + 0.015);
gain.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + dur);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + dur);
}
function playRotateSound() {
if (!audioEnabled) return;
initAudio();
if (!audioCtx) return;
const notes = [330, 392, 523.25, 659.25];
const f = notes[Math.abs(currentIndex) % notes.length];
playTone(f, 0.25, 'sine', 0.2);
}
function playFlipSound() {
if (!audioEnabled) return;
initAudio();
if (!audioCtx) return;
[587.33, 880].forEach((f, i) => {
setTimeout(() => {
playTone(f, 0.22, 'triangle', 0.18);
}, i * 50);
});
}
// --- 2. 3D Cylindrical Ring Placement ---
function arrangeCards() {
cards.forEach((card, index) => {
const cardAngle = anglePerCard * index;
card.style.transform = `rotateY(${cardAngle}deg) translateZ(${radius}px)`;
card.dataset.index = index;
});
}
function renderStepperDots() {
cardDots.innerHTML = '';
for (let i = 0; i < totalCards; i++) {
const dot = document.createElement('div');
dot.className = `dot-indicator ${i === currentIndex ? 'active' : ''}`;
dot.addEventListener('click', () => {
rotateToIndex(i);
});
cardDots.appendChild(dot);
}
}
function updateStepperDots() {
const dots = cardDots.querySelectorAll('.dot-indicator');
dots.forEach((dot, i) => {
if (i === currentIndex) dot.classList.add('active');
else dot.classList.remove('active');
});
}
function rotateCarousel() {
carousel.style.transform = `rotateY(${theta}deg)`;
// Calculate current front card index
const norm = ((-theta / anglePerCard) % totalCards + totalCards) % totalCards;
currentIndex = Math.round(norm) % totalCards;
updateStepperDots();
playRotateSound();
}
function rotateToIndex(index) {
theta = -index * anglePerCard;
rotateCarousel();
}
function nextCard() {
theta -= anglePerCard;
rotateCarousel();
}
function prevCard() {
theta += anglePerCard;
rotateCarousel();
}
function flipCard(card) {
card.classList.toggle('flipped');
playFlipSound();
}
// --- 3. Drag & Touch Gesture Controls ---
carouselContainer.addEventListener('mousedown', (e) => {
isDragging = true;
startX = e.pageX;
currentDragTheta = theta;
stopAutoPlay();
});
window.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const diffX = e.pageX - startX;
theta = currentDragTheta + diffX * 0.35;
carousel.style.transform = `rotateY(${theta}deg)`;
});
window.addEventListener('mouseup', (e) => {
if (!isDragging) return;
isDragging = false;
const diffX = e.pageX - startX;
if (Math.abs(diffX) > 25) {
if (diffX > 0) prevCard();
else nextCard();
} else {
// Snap to nearest card
theta = Math.round(theta / anglePerCard) * anglePerCard;
rotateCarousel();
}
});
// Touch Support
carouselContainer.addEventListener('touchstart', (e) => {
if (e.touches.length === 1) {
isDragging = true;
startX = e.touches[0].pageX;
currentDragTheta = theta;
stopAutoPlay();
}
}, { passive: true });
window.addEventListener('touchmove', (e) => {
if (!isDragging || e.touches.length !== 1) return;
const diffX = e.touches[0].pageX - startX;
theta = currentDragTheta + diffX * 0.35;
carousel.style.transform = `rotateY(${theta}deg)`;
}, { passive: true });
window.addEventListener('touchend', (e) => {
if (!isDragging) return;
isDragging = false;
theta = Math.round(theta / anglePerCard) * anglePerCard;
rotateCarousel();
});
// Card Click to Flip
cards.forEach(card => {
card.addEventListener('click', (e) => {
const idx = parseInt(card.dataset.index);
if (idx === currentIndex) {
flipCard(card);
} else {
rotateToIndex(idx);
}
});
});
// Navigation Arrows
prevBtn.addEventListener('click', prevCard);
nextBtn.addEventListener('click', nextCard);
// Keyboard Navigation
window.addEventListener('keydown', (e) => {
if (e.key === 'ArrowLeft') {
prevCard();
} else if (e.key === 'ArrowRight') {
nextCard();
} else if (e.key === ' ' || e.key === 'Enter') {
const frontCard = document.querySelector(`.memory-card[data-index="${currentIndex}"]`);
if (frontCard) flipCard(frontCard);
}
});
// --- 4. Auto-Play Rotation ---
function startAutoPlay() {
autoPlay = true;
autoPlayIcon.className = 'fas fa-pause';
if (autoPlayTimer) clearInterval(autoPlayTimer);
autoPlayTimer = setInterval(() => {
nextCard();
}, 3200 / autoPlaySpeed);
}
function stopAutoPlay() {
autoPlay = false;
autoPlayIcon.className = 'fas fa-play';
if (autoPlayTimer) {
clearInterval(autoPlayTimer);
autoPlayTimer = null;
}
}
btnAutoPlayToggle.addEventListener('click', () => {
if (autoPlay) stopAutoPlay();
else startAutoPlay();
});
// --- 5. Sliders & Customization ---
sliderRadius.addEventListener('input', (e) => {
radius = parseInt(e.target.value);
valRadius.innerText = `${radius}px`;
arrangeCards();
});
sliderPerspective.addEventListener('input', (e) => {
const val = parseInt(e.target.value);
valPerspective.innerText = `${val}px`;
carouselContainer.style.perspective = `${val}px`;
});
presetBtns.forEach(btn => {
btn.addEventListener('click', () => {
presetBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
autoPlaySpeed = parseFloat(btn.dataset.speed) || 1.0;
if (autoPlay) startAutoPlay();
});
});
// Dock Tabs
dockTabs.forEach(tab => {
tab.addEventListener('click', () => {
dockTabs.forEach(t => t.classList.remove('active'));
tabPanels.forEach(p => p.classList.remove('active'));
tab.classList.add('active');
const panel = document.getElementById(`panel${tab.dataset.tab.charAt(0).toUpperCase() + tab.dataset.tab.slice(1)}`);
if (panel) panel.classList.add('active');
bottomDock.classList.remove('collapsed');
dockChevron.className = 'fas fa-chevron-down';
});
});
btnToggleDock.addEventListener('click', () => {
bottomDock.classList.toggle('collapsed');
dockChevron.className = bottomDock.classList.contains('collapsed') ? 'fas fa-chevron-up' : 'fas fa-chevron-down';
});
// Audio Toggle
btnAudioToggle.addEventListener('click', () => {
audioEnabled = !audioEnabled;
btnAudioToggle.innerHTML = audioEnabled ? '<i class="fas fa-volume-up"></i>' : '<i class="fas fa-volume-xmark"></i>';
btnAudioToggle.style.color = audioEnabled ? '#ffffff' : '#94a3b8';
});
// Theme Toggle
btnThemeToggle.addEventListener('click', () => {
themeIndex = (themeIndex + 1) % themeClasses.length;
app.className = 'cosmic-carousel-app ' + themeClasses[themeIndex];
});
// Celebration Mode ❤️🎂✨
btnCelebrate.addEventListener('click', () => {
fireCelebrationParticles();
[523.25, 659.25, 783.99, 1046.50].forEach((f, i) => {
setTimeout(() => playTone(f, 0.3, 'triangle', 0.25), i * 70);
});
});
// --- 6. Celebration Particles (Hearts, Balloons, Stars) ---
const celCtx = celebrationCanvas.getContext('2d');
let particles = [];
let animFrame = null;
function resizeCanvas() {
celebrationCanvas.width = window.innerWidth;
celebrationCanvas.height = window.innerHeight;
}
resizeCanvas();
window.addEventListener('resize', () => {
resizeCanvas();
radius = window.innerWidth <= 768 ? 280 : 400;
arrangeCards();
});
function fireCelebrationParticles() {
particles = [];
const symbols = ['❤️', '🎂', '✨', '💖', '🌟', '🚀', '🔮'];
for (let i = 0; i < 65; i++) {
particles.push({
x: window.innerWidth / 2,
y: window.innerHeight * 0.5,
vx: (Math.random() - 0.5) * 16,
vy: -Math.random() * 14 - 4,
symbol: symbols[Math.floor(Math.random() * symbols.length)],
size: Math.random() * 16 + 18,
rotation: Math.random() * Math.PI * 2,
vRot: (Math.random() - 0.5) * 0.1,
life: 1.0
});
}
if (!animFrame) {
animateParticles();
}
}
function animateParticles() {
celCtx.clearRect(0, 0, celebrationCanvas.width, celebrationCanvas.height);
let active = 0;
particles.forEach(p => {
if (p.life > 0) {
active++;
p.x += p.vx;
p.y += p.vy;
p.vy += 0.3; // Gravity
p.rotation += p.vRot;
p.life -= 0.012;
celCtx.save();
celCtx.translate(p.x, p.y);
celCtx.rotate(p.rotation);
celCtx.globalAlpha = Math.max(0, p.life);
celCtx.font = `${p.size}px sans-serif`;
celCtx.textAlign = 'center';
celCtx.textBaseline = 'middle';
celCtx.fillText(p.symbol, 0, 0);
celCtx.restore();
}
});
if (active > 0) {
animFrame = requestAnimationFrame(animateParticles);
} else {
celCtx.clearRect(0, 0, celebrationCanvas.width, celebrationCanvas.height);
animFrame = null;
}
}
// Boot
arrangeCards();
renderStepperDots();
})();3. Core JavaScript Concepts You Learned
Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.
Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.
Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.
Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.
Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.
Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.
4. How to Run Locally on Your Computer
- Download the project ZIP: Click the Download Project (.ZIP) button at the top or in the live runner to save
3d-carousel-animation-project.zip. - Extract the archive: Unzip the downloaded file. Inside you will find separate
index.html,style.css,script.js,index-standalone.html, and a comprehensiveREADME.md. - Open in any browser: Simply double-click
index.html(orindex-standalone.html) to open and test immediately in Chrome, Firefox, Safari, or Edge. - Edit in Visual Studio Code: Open the extracted folder in VS Code. Right-click
index.htmland select "Open with Live Server" to enjoy instant live reloading as you modify HTML, CSS, or JS!
Bonus Challenges for Learners
Ready to level up? Try adding these extra features on your own:
- Add sound effects or audio feedback for user button clicks.
- Add custom theme color customizer with CSS variables.
- Export the results to PDF or copy as a formatted text summary.