3D Cosmic Memory Carousel & Flip Card Animation

A futuristic 3D cylindrical ring carousel in deep cosmic space: features 6 interactive holographic memory cards revolving in 3D perspective with touch/mouse drag, 3D card flip mechanics, neon glow shaders, and celebration effects ❤️🎂✨.

Difficulty: Intermediate Est. Time: 40 mins Vanilla JS (ES6+) Includes HTML, CSS, JS & README (.ZIP)

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.

HTML (index.html)
<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 &amp; 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 &lsquo;Hello, World!&rsquo; 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&mdash;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 &amp; 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 &amp; 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&mdash;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.

CSS (style.css)
/* 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:

JavaScript (script.js)
(() => {
  // 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

CSS 3D Transform Matrix

Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.

Cylindrical Radial Coordinate Math

Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.

3D Card Flip Mechanics

Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.

Drag & Swipe Inertia Physics

Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.

Web Audio Synthesizer

Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.

Celebration Particle Cannon

Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.

4. How to Run Locally on Your Computer

  1. 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.
  2. Extract the archive: Unzip the downloaded file. Inside you will find separate index.html, style.css, script.js, index-standalone.html, and a comprehensive README.md.
  3. Open in any browser: Simply double-click index.html (or index-standalone.html) to open and test immediately in Chrome, Firefox, Safari, or Edge.
  4. Edit in Visual Studio Code: Open the extracted folder in VS Code. Right-click index.html and 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.

Other Beginner Projects You Might Like

Advanced45 mins
Creative & 3D WebGL

3D Interactive Birthday Heart Tree Surprise

I turned HTML, CSS, and JavaScript into a special birthday surprise. An interactive 3D WebGL heart tree with pull-and-release slingshot, procedural branch growth, 600+ blooming heart petals, and music box melody.

Three.js WebGLProcedural 3D TreesPhysics Slingshot+3
Advanced45 mins
Creative & 3D WebGL

3D 'Add to Cart' Product Delivery Truck & Box Animation

Viral 3D WebGL e-commerce interaction: starts with a floating 3D 'Add to Cart 🛒' button in center stage. When clicked, it morphs into an unfolding cardboard box, glides to the side, and launches the parcel along a 3D parabolic trajectory curve into the open cargo bay of a yellow delivery truck, which seals its doors and drives off for express 24h delivery.

Three.js 3D WebGLInteractive 3D Button MorphingFlap Physics & Box Unfolding+4
Advanced35 mins
Creative & 3D WebGL

3D Radial Share Arc Button Animation

Viral UI component inspired by @_code_and_chill_: clicking the 'SHARE' capsule button triggers a smooth spring-physics radial arc splay of 5 social channel discs (𝕏, LinkedIn, WhatsApp, Email, Copy Link) using trigonometric CSS placement formulas.

Trigonometric Radial Arc Splay3D Interactive Perspective TiltSpring Physics & Harmonic Damping+3