# Dropping Cards Stack

> Cards drop into a stacked pile with physics-like motion — a playful sequence for features, products, or story beats.

**Definition:** Dropping Cards Stack is a cards UI animation from collections.html. It is built with HTML, CSS, JavaScript, and GSAP, ships with a live preview, and is free to copy into personal and commercial projects.

## Facts
- Category: Cards
- Stack: HTML, CSS, JavaScript, and GSAP
- Added: 2026-08-02
- Canonical page: https://html.bosconvertpro.com/collection/dropping-cards-stack
- Full-page preview: https://html.bosconvertpro.com/collection/dropping-cards-stack/full
- Live demo: https://html.bosconvertpro.com/demos/dropping-cards-stack/index.html
- Markdown mirror: https://html.bosconvertpro.com/collection/dropping-cards-stack/index.md
- License: https://html.bosconvertpro.com/terms

## How to use
1. Open https://html.bosconvertpro.com/collection/dropping-cards-stack/full to preview the animation in full screen.
2. Copy index.html, styles.css, and script.js from the code tabs on https://html.bosconvertpro.com/collection/dropping-cards-stack.
3. Paste the files into your project and adjust timing, easing, and content as needed.

## FAQ
### What is Dropping Cards Stack?

Dropping Cards Stack is a cards UI animation from collections.html. Cards drop into a stacked pile with physics-like motion — a playful sequence for features, products, or story beats.

### What is Dropping Cards Stack built with?

Dropping Cards Stack is built with HTML, CSS, JavaScript, and GSAP. The source for every file is shown on the animation page and can be copied straight into your project.

### How do I use Dropping Cards Stack in my project?

Open the animation page (https://html.bosconvertpro.com/collection/dropping-cards-stack), preview it live, then copy index.html, styles.css, and script.js from the code tabs. Paste the files into your project and adjust timing, easing, and content as needed. A standalone demo runs at https://html.bosconvertpro.com/demos/dropping-cards-stack/index.html.

### Is Dropping Cards Stack free to use?

Yes. The Dropping Cards Stack demo code may be used in personal and commercial projects. You may not resell or redistribute the collection itself as a competing template pack. Full terms: https://html.bosconvertpro.com/terms.

## Source — index.html
````html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Dropping Cards Stack</title>
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link rel="preconnect" href="https://images.unsplash.com" />
    <link
      href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
      rel="stylesheet"
    />
    <link rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <main class="page">
      <div data-dropping-stack-init class="dropping-stack">
        <div data-dropping-stack-collection class="dropping-stack__collection">
          <div class="dropping-stack__list">
            <div data-dropping-stack-item class="dropping-stack__item">
              <div class="dropping-stack-card">
                <div class="dropping-stack-card__before"></div>
                <div class="dropping-stack-card__content">
                  <div class="dropping-stack-card__start">
                    <div class="dropping-stack-card__visual">
                      <div class="dropping-stack-card__visual-before"></div>
                      <img
                        src="https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?auto=format&fit=crop&w=800&q=80"
                        loading="lazy"
                        alt="Abstract fluid art"
                        class="dropping-stack-card__visual-img"
                        draggable="false"
                      />
                    </div>
                    <div class="dropping-stack-card__words">
                      <p class="dropping-stack-card__p">Art Direction</p>
                      <p class="dropping-stack-card__p">Brand Systems</p>
                      <p class="dropping-stack-card__p">Visual Language</p>
                    </div>
                  </div>
                  <div class="dropping-stack-card__end">
                    <h3 class="dropping-stack-card__h">Creative Direction.</h3>
                  </div>
                </div>
              </div>
            </div>

            <div data-dropping-stack-item class="dropping-stack__item">
              <div class="dropping-stack-card is--light">
                <div class="dropping-stack-card__before"></div>
                <div class="dropping-stack-card__content">
                  <div class="dropping-stack-card__start">
                    <div class="dropping-stack-card__visual">
                      <div class="dropping-stack-card__visual-before"></div>
                      <img
                        src="https://images.unsplash.com/photo-1460925895917-afdab827c52f?auto=format&fit=crop&w=800&q=80"
                        loading="lazy"
                        alt="Analytics dashboard on laptop"
                        class="dropping-stack-card__visual-img"
                        draggable="false"
                      />
                    </div>
                    <div class="dropping-stack-card__words">
                      <p class="dropping-stack-card__p">Growth Loops</p>
                      <p class="dropping-stack-card__p">Paid Media</p>
                      <p class="dropping-stack-card__p">Content Engine</p>
                      <p class="dropping-stack-card__p">Conversion</p>
                      <p class="dropping-stack-card__p">Analytics</p>
                    </div>
                  </div>
                  <div class="dropping-stack-card__end">
                    <h3 class="dropping-stack-card__h">Growth Marketing.</h3>
                  </div>
                </div>
              </div>
            </div>

            <div data-dropping-stack-item class="dropping-stack__item">
              <div class="dropping-stack-card is--purple">
                <div class="dropping-stack-card__before"></div>
                <div class="dropping-stack-card__content">
                  <div class="dropping-stack-card__start">
                    <div class="dropping-stack-card__visual">
                      <div class="dropping-stack-card__visual-before"></div>
                      <img
                        src="https://images.unsplash.com/photo-1581291518633-83b4ebd1d83e?auto=format&fit=crop&w=800&q=80"
                        loading="lazy"
                        alt="UI design sketches on desk"
                        class="dropping-stack-card__visual-img"
                        draggable="false"
                      />
                    </div>
                    <div class="dropping-stack-card__words">
                      <p class="dropping-stack-card__p">Research</p>
                      <p class="dropping-stack-card__p">Prototypes</p>
                      <p class="dropping-stack-card__p">Us Flows</p>
                    </div>
                  </div>
                  <div class="dropping-stack-card__end">
                    <h3 class="dropping-stack-card__h">Product Design.</h3>
                  </div>
                </div>
              </div>
            </div>

            <div data-dropping-stack-item class="dropping-stack__item">
              <div class="dropping-stack-card is--pink">
                <div class="dropping-stack-card__before"></div>
                <div class="dropping-stack-card__content">
                  <div class="dropping-stack-card__start">
                    <div class="dropping-stack-card__visual">
                      <div class="dropping-stack-card__visual-before"></div>
                      <img
                        src="https://images.unsplash.com/photo-1550745165-9bc0b252726f?auto=format&fit=crop&w=800&q=80"
                        loading="lazy"
                        alt="Retro gaming tech setup"
                        class="dropping-stack-card__visual-img"
                        draggable="false"
                      />
                    </div>
                    <div class="dropping-stack-card__words">
                      <p class="dropping-stack-card__p">Motion Design</p>
                      <p class="dropping-stack-card__p">Micro Interactions</p>
                      <p class="dropping-stack-card__p">3D Assets</p>
                      <p class="dropping-stack-card__p">Prototyping</p>
                    </div>
                  </div>
                  <div class="dropping-stack-card__end">
                    <h3 class="dropping-stack-card__h">Motion Studio.</h3>
                  </div>
                </div>
              </div>
            </div>

            <div data-dropping-stack-item class="dropping-stack__item">
              <div class="dropping-stack-card is--dark">
                <div class="dropping-stack-card__before"></div>
                <div class="dropping-stack-card__content">
                  <div class="dropping-stack-card__start">
                    <div class="dropping-stack-card__visual">
                      <div class="dropping-stack-card__visual-before"></div>
                      <img
                        src="https://images.unsplash.com/photo-1498050108023-c5249f4df085?auto=format&fit=crop&w=800&q=80"
                        loading="lazy"
                        alt="Developer workspace with code"
                        class="dropping-stack-card__visual-img"
                        draggable="false"
                      />
                    </div>
                    <div class="dropping-stack-card__words">
                      <p class="dropping-stack-card__p">Front-end</p>
                      <p class="dropping-stack-card__p">Performance</p>
                      <p class="dropping-stack-card__p">Design Systems</p>
                    </div>
                  </div>
                  <div class="dropping-stack-card__end">
                    <h3 class="dropping-stack-card__h">Web Experiences.</h3>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>

        <div class="dropping-stack__controls">
          <button
            type="button"
            data-dropping-stack-prev
            class="dropping-stack__control is--prev"
            aria-label="Previous card"
          >
            <span class="dropping-stack__control-circle is--prev">
              <svg
                xmlns="http://www.w3.org/2000/svg"
                width="100%"
                viewBox="0 0 24 24"
                fill="none"
                class="dropping-stack__control-svg"
                aria-hidden="true"
              >
                <path
                  d="M15 6L9 12L15 18"
                  stroke="currentColor"
                  stroke-width="2"
                  stroke-linecap="round"
                  stroke-linejoin="round"
                />
              </svg>
            </span>
          </button>
          <button
            type="button"
            data-dropping-stack-next
            class="dropping-stack__control"
            aria-label="Next card"
          >
            <span class="dropping-stack__control-circle">
              <svg
                xmlns="http://www.w3.org/2000/svg"
                width="100%"
                viewBox="0 0 24 24"
                fill="none"
                class="dropping-stack__control-svg"
                aria-hidden="true"
              >
                <path
                  d="M9 6L15 12L9 18"
                  stroke="currentColor"
                  stroke-width="2"
                  stroke-linecap="round"
                  stroke-linejoin="round"
                />
              </svg>
            </span>
          </button>
        </div>
      </div>
    </main>

    <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.1/dist/gsap.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.1/dist/Draggable.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.1/dist/CustomEase.min.js"></script>
    <script src="script.js"></script>
  </body>
</html>
````

## Source — styles.css
````css
*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  height: 100%;
  -webkit-text-size-adjust: 100%;
}

body {
  min-height: 100%;
  color: #201d1d;
  background-color: #568203;
  background-image:
    radial-gradient(
      ellipse 110% 90% at 15% 8%,
      #d4ff4a 0%,
      transparent 52%
    ),
    radial-gradient(
      ellipse 90% 75% at 92% 88%,
      #3d6b1f 0%,
      transparent 55%
    ),
    radial-gradient(
      ellipse 70% 55% at 48% 55%,
      #a8d930 0%,
      transparent 58%
    ),
    linear-gradient(
      155deg,
      #c8f03a 0%,
      #9ed420 28%,
      #6fa812 55%,
      #568203 78%,
      #3f6508 100%
    );
  background-attachment: fixed;
  font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 1em;
  font-weight: 400;
  line-height: 1.25;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

img {
  display: block;
  max-width: 100%;
  border: 0;
  -webkit-user-drag: none;
  user-select: none;
}

.page {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2em 1.25em;
  overflow: hidden;
  background: transparent;
}

.dropping-stack {
  grid-column-gap: 2em;
  grid-row-gap: 2em;
  flex-flow: column;
  align-items: center;
  display: flex;
}

.dropping-stack__collection {
  padding-bottom: 7.5em;
  padding-right: 7.5em;
}

.dropping-stack__list {
  justify-content: center;
  align-items: center;
  display: flex;
  position: relative;
}

.dropping-stack__item {
  will-change: transform, opacity;
  -webkit-user-select: none;
  user-select: none;
  position: absolute;
}

.dropping-stack__item:nth-child(1) {
  position: relative;
}

.dropping-stack-card {
  color: #1a1410;
  background-color: #f0b85a;
  background-image:
    radial-gradient(ellipse 90% 70% at 12% 18%, rgba(255, 245, 210, 0.95) 0%, transparent 55%),
    radial-gradient(ellipse 70% 60% at 88% 82%, rgba(232, 140, 48, 0.65) 0%, transparent 50%),
    linear-gradient(145deg, #ffe29a 0%, #f5c06a 38%, #e89a3c 72%, #d4842a 100%);
  border-radius: 1.25em;
  width: min(50em, 100vw - 10em);
  position: relative;
  overflow: hidden;
}

.dropping-stack-card.is--light {
  color: #161616;
  background-color: #ececec;
  background-image:
    radial-gradient(ellipse 85% 65% at 15% 12%, rgba(255, 255, 255, 1) 0%, transparent 55%),
    radial-gradient(ellipse 60% 50% at 90% 90%, rgba(200, 205, 215, 0.55) 0%, transparent 50%),
    linear-gradient(150deg, #ffffff 0%, #f3f4f6 42%, #e6e8ed 78%, #d8dbe3 100%);
}

.dropping-stack-card.is--purple {
  color: #f7f4ff;
  background-color: #6b4fd8;
  background-image:
    radial-gradient(ellipse 90% 70% at 18% 15%, rgba(196, 170, 255, 0.75) 0%, transparent 52%),
    radial-gradient(ellipse 65% 55% at 92% 88%, rgba(72, 40, 180, 0.7) 0%, transparent 48%),
    linear-gradient(148deg, #b8a0ff 0%, #8b6cf0 36%, #6a4adb 68%, #4a2fc0 100%);
}

.dropping-stack-card.is--pink {
  color: #2a1528;
  background-color: #e4bdf2;
  background-image:
    radial-gradient(ellipse 90% 70% at 14% 16%, rgba(255, 236, 250, 0.95) 0%, transparent 55%),
    radial-gradient(ellipse 65% 55% at 88% 86%, rgba(214, 120, 210, 0.55) 0%, transparent 50%),
    linear-gradient(148deg, #fde8ff 0%, #f0c6f5 35%, #e0a0e8 68%, #c97ad4 100%);
}

.dropping-stack-card.is--dark {
  color: #f4f4f8;
  background-color: #0c0c14;
  background-image:
    radial-gradient(ellipse 90% 70% at 16% 12%, rgba(70, 78, 120, 0.55) 0%, transparent 52%),
    radial-gradient(ellipse 70% 55% at 90% 90%, rgba(20, 18, 40, 0.9) 0%, transparent 48%),
    linear-gradient(155deg, #1c1e2e 0%, #12131c 40%, #0a0a12 75%, #050508 100%);
}

.dropping-stack-card__before {
  padding-top: 62.5%;
}

.dropping-stack-card__content {
  flex-flow: column;
  justify-content: space-between;
  width: 100%;
  height: 100%;
  padding: 3em;
  display: flex;
  position: absolute;
  top: 0;
  left: 0;
}

.dropping-stack-card__start {
  justify-content: space-between;
  display: flex;
}

.dropping-stack-card__visual {
  background-color: #0000001a;
  border-radius: 0.5em;
  width: 35%;
  position: relative;
  overflow: hidden;
  isolation: isolate;
  transform: translateZ(0);
  backface-visibility: hidden;
}

.dropping-stack-card__visual-before {
  padding-top: 62.5%;
}

.dropping-stack-card__visual-img {
  object-fit: cover;
  border-radius: inherit;
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: center center;
  will-change: transform, opacity, filter;
  backface-visibility: hidden;
}

.dropping-stack-card__words {
  width: 45%;
  overflow: hidden;
}

.dropping-stack-card__p {
  margin-bottom: 0;
  font-size: 1.125em;
  font-weight: 500;
  line-height: 1.4;
  will-change: transform, opacity, filter;
  backface-visibility: hidden;
}

.dropping-stack-card__end {
  display: flex;
  overflow: hidden;
}

.dropping-stack-card__h {
  letter-spacing: -0.03em;
  margin-top: 0;
  margin-bottom: 0;
  font-size: 4.75em;
  font-weight: 600;
  line-height: 0.9;
  will-change: transform, opacity, filter, clip-path;
  backface-visibility: hidden;
  display: block;
}

.dropping-stack__controls {
  display: flex;
  align-items: center;
  gap: 0.75em;
}

.dropping-stack__control {
  appearance: none;
  border: 0;
  padding: 0;
  margin: 0;
  background: transparent;
  cursor: pointer;
  border-radius: 999px;
  -webkit-tap-highlight-color: transparent;
}

.dropping-stack__control-circle {
  color: #0a0a0a;
  background: rgba(255, 255, 255, 0.92);
  border: 1px solid rgba(0, 0, 0, 0.06);
  box-shadow:
    0 1px 2px rgba(0, 0, 0, 0.06),
    0 8px 24px rgba(0, 0, 0, 0.1);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-radius: 999px;
  flex: none;
  justify-content: center;
  align-items: center;
  width: 3.25em;
  height: 3.25em;
  display: flex;
  position: relative;
  transition:
    transform 0.35s cubic-bezier(0.22, 1, 0.36, 1),
    background 0.25s ease,
    box-shadow 0.25s ease,
    color 0.25s ease;
  transform: translateY(0) scale(1);
}

.dropping-stack__control:hover .dropping-stack__control-circle {
  transform: translateY(-0.2em) scale(1.04);
  background: #ffffff;
  box-shadow:
    0 2px 4px rgba(0, 0, 0, 0.08),
    0 12px 28px rgba(0, 0, 0, 0.14);
}

.dropping-stack__control:active .dropping-stack__control-circle {
  transform: translateY(0) scale(0.96);
}

.dropping-stack__control-circle.is--prev {
  color: rgba(10, 10, 10, 0.72);
  background: rgba(255, 255, 255, 0.38);
  border-color: rgba(255, 255, 255, 0.35);
  box-shadow:
    0 1px 2px rgba(0, 0, 0, 0.04),
    0 6px 16px rgba(0, 0, 0, 0.06);
}

.dropping-stack__control.is--prev:hover .dropping-stack__control-circle {
  color: #0a0a0a;
  background: rgba(255, 255, 255, 0.72);
}

.dropping-stack__control-svg {
  width: 42%;
  height: 42%;
  display: block;
}

.dropping-stack__control:focus-visible .dropping-stack__control-circle {
  outline: 2px solid #0a0a0a;
  outline-offset: 3px;
}

@media screen and (max-width: 991px) {
  .dropping-stack-card__before {
    padding-top: 75%;
  }
}

@media screen and (max-width: 767px) {
  .dropping-stack__collection {
    padding-bottom: 4.5em;
    padding-right: 4.5em;
  }

  .dropping-stack-card {
    width: min(50em, 100vw - 6.5em);
  }

  .dropping-stack-card__before {
    padding-top: 150%;
  }

  .dropping-stack-card__content {
    padding: 7.5vw;
  }

  .dropping-stack-card__start {
    grid-column-gap: 2em;
    grid-row-gap: 2em;
    flex-flow: column;
  }

  .dropping-stack-card__h {
    font-size: 10vw;
  }

  .dropping-stack-card__visual {
    width: 60%;
  }

  .dropping-stack-card__words {
    width: 100%;
  }
}
````

## Source — script.js
````javascript
gsap.registerPlugin(Draggable, CustomEase);

CustomEase.create("stackEase", "0.625, 0.05, 0, 1");
CustomEase.create("revealOut", "0.22, 1, 0.36, 1");
CustomEase.create("revealSoft", "0.33, 1, 0.68, 1");
CustomEase.create("exitSoft", "0.4, 0, 0.2, 1");

function initDroppingCardsStack() {
  const stacks = document.querySelectorAll("[data-dropping-stack-init]");
  if (!stacks.length) return;

  const visibleCount = 4;
  const minTotalForLoop = 5;
  const duration = 0.75;
  const mainEase = "stackEase";
  const dragThresholdPercent = 20;

  const getUnitValue = (val, depth) => {
    const num = parseFloat(val) || 0;
    const unit = String(val).replace(/[0-9.-]/g, "") || "px";
    return num * depth + unit;
  };

  stacks.forEach((stackEl) => {
    const nextBtn = stackEl.querySelector("[data-dropping-stack-next]");
    const prevBtn = stackEl.querySelector("[data-dropping-stack-prev]");
    const list = stackEl.querySelector(".dropping-stack__list");
    let cards = Array.from(list.querySelectorAll("[data-dropping-stack-item]"));
    if (cards.length < 3) return;

    const originalCount = cards.length;

    if (cards.length < minTotalForLoop) {
      const setsNeeded = Math.ceil(minTotalForLoop / originalCount);
      const clonesToAdd = setsNeeded * originalCount - originalCount;

      for (let i = 0; i < clonesToAdd; i++) {
        const clone = cards[i % originalCount].cloneNode(true);
        clone.setAttribute("aria-hidden", "true");
        list.appendChild(clone);
      }

      cards = Array.from(list.querySelectorAll("[data-dropping-stack-item]"));
    }

    const total = cards.length;
    let activeIndex = 0;
    let isAnimating = false;
    let dragCard = null;
    let draggableInstance = null;
    let limitX = 1;
    let limitY = 1;
    let offsetX = "0em";
    let offsetY = "0em";
    let isActive = false;
    let contentTween = null;

    const mod = (n, m) => ((n % m) + m) % m;
    const cardAt = (offset) => cards[mod(activeIndex + offset, total)];

    function getContent(card) {
      return {
        visual: card.querySelector(".dropping-stack-card__visual"),
        image: card.querySelector(".dropping-stack-card__visual-img"),
        words: card.querySelectorAll(".dropping-stack-card__p"),
        title: card.querySelector(".dropping-stack-card__h"),
      };
    }

    function setContentIdle(card) {
      const { visual, image, words, title } = getContent(card);
      gsap.set(visual, {
        opacity: 1,
        scale: 1,
        y: 0,
        filter: "blur(0px)",
        force3D: true,
      });
      gsap.set(image, {
        scale: 1,
        opacity: 1,
        yPercent: 0,
        filter: "blur(0px)",
        force3D: true,
      });
      gsap.set(words, {
        opacity: 1,
        y: 0,
        filter: "blur(0px)",
        force3D: true,
      });
      gsap.set(title, {
        opacity: 1,
        yPercent: 0,
        filter: "blur(0px)",
        clipPath: "inset(0% 0% 0% 0%)",
        force3D: true,
      });
    }

    function setContentHidden(card) {
      const { visual, image, words, title } = getContent(card);
      gsap.set(visual, {
        opacity: 0,
        scale: 0.96,
        y: 14,
        filter: "blur(6px)",
        force3D: true,
      });
      gsap.set(image, {
        scale: 1.12,
        opacity: 0.65,
        yPercent: 8,
        filter: "blur(8px)",
        force3D: true,
      });
      gsap.set(words, {
        opacity: 0,
        y: 12,
        filter: "blur(4px)",
        force3D: true,
      });
      gsap.set(title, {
        opacity: 0,
        yPercent: 110,
        filter: "blur(6px)",
        clipPath: "inset(0% 0% 100% 0%)",
        force3D: true,
      });
    }

    function resetContent(card, immediate = true) {
      const { visual, image, words, title } = getContent(card);
      if (!visual && !title) return;

      gsap.killTweensOf([visual, image, title, ...words].filter(Boolean));

      if (immediate) {
        setContentHidden(card);
        return;
      }

      gsap
        .timeline({ defaults: { ease: "exitSoft", overwrite: "auto" } })
        .to(
          words,
          {
            opacity: 0,
            y: 8,
            filter: "blur(3px)",
            duration: 0.28,
            stagger: { each: 0.03, from: "end" },
          },
          0
        )
        .to(
          title,
          {
            opacity: 0,
            yPercent: 40,
            filter: "blur(4px)",
            clipPath: "inset(0% 0% 40% 0%)",
            duration: 0.32,
          },
          0.02
        )
        .to(
          visual,
          {
            opacity: 0,
            scale: 0.97,
            y: 10,
            filter: "blur(4px)",
            duration: 0.34,
          },
          0
        )
        .to(
          image,
          {
            scale: 1.08,
            opacity: 0.5,
            yPercent: 6,
            filter: "blur(6px)",
            duration: 0.34,
          },
          0
        );
    }

    function revealContent(card, delay = 0) {
      const { visual, image, words, title } = getContent(card);
      if (!visual && !title) return;

      if (contentTween) contentTween.kill();
      gsap.killTweensOf([visual, image, title, ...words].filter(Boolean));

      setContentHidden(card);

      contentTween = gsap.timeline({
        delay,
        defaults: { overwrite: "auto", force3D: true },
      });

      contentTween
        .to(
          visual,
          {
            opacity: 1,
            scale: 1,
            y: 0,
            filter: "blur(0px)",
            duration: 0.95,
            ease: "revealOut",
          },
          0
        )
        .to(
          image,
          {
            scale: 1,
            opacity: 1,
            yPercent: 0,
            filter: "blur(0px)",
            duration: 1.25,
            ease: "revealSoft",
          },
          0.04
        )
        .to(
          words,
          {
            opacity: 1,
            y: 0,
            filter: "blur(0px)",
            duration: 0.8,
            stagger: { each: 0.07, from: "start", ease: "none" },
            ease: "revealOut",
          },
          0.16
        )
        .to(
          title,
          {
            opacity: 1,
            yPercent: 0,
            filter: "blur(0px)",
            clipPath: "inset(0% 0% 0% 0%)",
            duration: 1,
            ease: "revealOut",
          },
          0.2
        );
    }

    function prepareAllContent() {
      cards.forEach((card) => setContentHidden(card));
    }

    function updateOffsetsFromPadding() {
      const collectionEl = stackEl.querySelector(
        "[data-dropping-stack-collection]"
      );
      if (!collectionEl) return;

      const styles = getComputedStyle(collectionEl);
      const padRight = parseFloat(styles.paddingRight) || 0;
      const padLeft = parseFloat(styles.paddingLeft) || 0;
      const padBottom = parseFloat(styles.paddingBottom) || 0;
      const padTop = parseFloat(styles.paddingTop) || 0;
      const steps = Math.max(1, visibleCount - 1);
      const usePadX = Math.max(padRight, padLeft);
      const usePadY = Math.max(padBottom, padTop);
      const signX = padLeft > padRight ? -1 : 1;
      const signY = padTop > padBottom ? -1 : 1;
      const xStep = (usePadX / steps) * signX;
      const yStep = (usePadY / steps) * signY;

      offsetX = xStep + "px";
      offsetY = yStep + "px";
    }

    function updateDragLimits() {
      if (!dragCard) return;
      const cardRect = dragCard.getBoundingClientRect();
      limitX = cardRect.width || 1;
      limitY = cardRect.height || 1;
    }

    function applyState({ playContent = false, contentDelay = 0.05 } = {}) {
      updateOffsetsFromPadding();

      cards.forEach((card) => {
        gsap.set(card, {
          opacity: 0,
          pointerEvents: "none",
          zIndex: 0,
          x: 0,
          y: 0,
          xPercent: 0,
          yPercent: 0,
        });
      });

      for (let depth = 0; depth < visibleCount; depth++) {
        const card = cardAt(depth);
        const xVal = getUnitValue(offsetX, depth);
        const yVal = getUnitValue(offsetY, depth);
        const state = {
          opacity: 1,
          zIndex: 999 - depth,
          pointerEvents: depth === 0 ? "auto" : "none",
        };

        if (offsetX.includes("%")) state.xPercent = parseFloat(xVal);
        else state.x = xVal;
        if (offsetY.includes("%")) state.yPercent = parseFloat(yVal);
        else state.y = yVal;

        gsap.set(card, state);

        if (depth === 0) {
          if (playContent) {
            revealContent(card, contentDelay);
          } else {
            setContentIdle(card);
          }
        } else {
          setContentHidden(card);
        }
      }

      dragCard = cardAt(0);
      gsap.set(dragCard, { touchAction: "none" });
      updateDragLimits();

      if (draggableInstance) {
        draggableInstance.kill();
        draggableInstance = null;
      }

      const magnetize = (raw, limit) => {
        const sign = Math.sign(raw) || 1;
        const abs = Math.abs(raw);
        const out = limit * Math.tanh(abs / limit);
        return sign * out;
      };

      draggableInstance = Draggable.create(dragCard, {
        type: "x,y",
        inertia: false,
        onPress: function () {
          if (isAnimating) return;
          gsap.killTweensOf(dragCard);
          gsap.set(dragCard, { zIndex: 2000, opacity: 1 });
        },
        onDrag: function () {
          if (isAnimating) return;
          const x = magnetize(this.x, limitX);
          const y = magnetize(this.y, limitY);
          gsap.set(dragCard, { x, y, opacity: 1 });
        },
        onRelease: function () {
          if (isAnimating) return;

          const currentX = gsap.getProperty(dragCard, "x");
          const currentY = gsap.getProperty(dragCard, "y");
          const movedXPercent = (Math.abs(currentX) / limitX) * 100;
          const movedYPercent = (Math.abs(currentY) / limitY) * 100;
          const movedPercent = Math.max(movedXPercent, movedYPercent);

          if (movedPercent >= dragThresholdPercent) {
            animateNext(true, currentX, currentY);
            return;
          }

          gsap.to(dragCard, {
            x: 0,
            y: 0,
            opacity: 1,
            duration: 1,
            ease: "elastic.out(1, 0.7)",
            onComplete: () => {
              applyState({ playContent: false });
            },
          });
        },
      })[0];
    }

    function animateNext(fromDrag = false, releaseX = 0, releaseY = 0) {
      if (isAnimating) return;
      isAnimating = true;

      const outgoing = cardAt(0);
      const incomingFront = cardAt(1);
      const incomingBack = cardAt(visibleCount);

      resetContent(outgoing, false);

      const tl = gsap.timeline({
        defaults: { duration, ease: mainEase },
        onComplete: () => {
          activeIndex = mod(activeIndex + 1, total);
          applyState({ playContent: false });
          isAnimating = false;
        },
      });

      gsap.set(outgoing, { zIndex: 2000, opacity: 1 });
      if (fromDrag) gsap.set(outgoing, { x: releaseX, y: releaseY });

      tl.to(outgoing, { yPercent: 200 }, 0);
      tl.to(
        outgoing,
        { opacity: 0, duration: duration * 0.2, ease: "none" },
        duration * 0.4
      );

      for (let depth = 1; depth < visibleCount; depth++) {
        const xVal = getUnitValue(offsetX, depth - 1);
        const yVal = getUnitValue(offsetY, depth - 1);
        const move = { zIndex: 999 - (depth - 1) };

        if (offsetX.includes("%")) move.xPercent = parseFloat(xVal);
        else move.x = xVal;
        if (offsetY.includes("%")) move.yPercent = parseFloat(yVal);
        else move.y = yVal;

        tl.to(cardAt(depth), move, 0);
      }

      const backX = getUnitValue(offsetX, visibleCount);
      const backY = getUnitValue(offsetY, visibleCount);
      const startX = getUnitValue(offsetX, visibleCount - 1);
      const startY = getUnitValue(offsetY, visibleCount - 1);

      const incomingSet = { opacity: 0, zIndex: 999 - visibleCount };
      if (offsetX.includes("%")) incomingSet.xPercent = parseFloat(backX);
      else incomingSet.x = backX;
      if (offsetY.includes("%")) incomingSet.yPercent = parseFloat(backY);
      else incomingSet.y = backY;
      gsap.set(incomingBack, incomingSet);

      const incomingTo = { opacity: 1 };
      if (offsetX.includes("%")) incomingTo.xPercent = parseFloat(startX);
      else incomingTo.x = startX;
      if (offsetY.includes("%")) incomingTo.yPercent = parseFloat(startY);
      else incomingTo.y = startY;
      tl.to(incomingBack, incomingTo, 0);

      tl.add(() => {
        revealContent(incomingFront, 0);
      }, duration * 0.28);
    }

    function animatePrev() {
      if (isAnimating) return;
      isAnimating = true;

      const outgoingFront = cardAt(0);
      const incomingTop = cardAt(-1);
      const leavingBack = cardAt(visibleCount - 1);

      resetContent(outgoingFront, false);

      const tl = gsap.timeline({
        defaults: { duration, ease: mainEase },
        onComplete: () => {
          activeIndex = mod(activeIndex - 1, total);
          applyState({ playContent: false });
          isAnimating = false;
        },
      });

      gsap.set(leavingBack, { zIndex: 1 });
      gsap.set(incomingTop, {
        opacity: 0,
        x: 0,
        xPercent: 0,
        yPercent: -200,
        zIndex: 2000,
      });

      resetContent(incomingTop, true);

      tl.to(incomingTop, { yPercent: 0 }, 0);
      tl.to(
        incomingTop,
        { opacity: 1, duration: duration * 0.2, ease: "none" },
        duration * 0.3
      );

      for (let depth = 0; depth < visibleCount - 1; depth++) {
        const xVal = getUnitValue(offsetX, depth + 1);
        const yVal = getUnitValue(offsetY, depth + 1);
        const move = { zIndex: 999 - (depth + 1) };

        if (offsetX.includes("%")) move.xPercent = parseFloat(xVal);
        else move.x = xVal;
        if (offsetY.includes("%")) move.yPercent = parseFloat(yVal);
        else move.y = yVal;

        tl.to(cardAt(depth), move, 0);
      }

      const backX = getUnitValue(offsetX, visibleCount);
      const backY = getUnitValue(offsetY, visibleCount);
      const hideBack = { opacity: 0 };

      if (offsetX.includes("%")) hideBack.xPercent = parseFloat(backX);
      else hideBack.x = backX;
      if (offsetY.includes("%")) hideBack.yPercent = parseFloat(backY);
      else hideBack.y = backY;

      tl.to(leavingBack, hideBack, 0);

      tl.add(() => {
        revealContent(incomingTop, 0);
      }, duration * 0.32);
    }

    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          const wasActive = isActive;
          isActive = entry.isIntersecting && entry.intersectionRatio >= 0.6;

          if (isActive && !wasActive && !isAnimating) {
            revealContent(cardAt(0), 0.05);
          }
        });
      },
      { threshold: [0, 0.6, 1] }
    );
    observer.observe(stackEl);

    const onKeyDown = (e) => {
      if (!isActive) return;
      if (isAnimating) return;

      const tag =
        e.target && e.target.tagName ? e.target.tagName.toLowerCase() : "";
      const isTyping =
        tag === "input" ||
        tag === "textarea" ||
        tag === "select" ||
        (e.target && e.target.isContentEditable);
      if (isTyping) return;

      if (e.key === "ArrowRight") {
        e.preventDefault();
        animateNext(false);
      }
      if (e.key === "ArrowLeft") {
        e.preventDefault();
        animatePrev();
      }
    };

    window.addEventListener("keydown", onKeyDown);
    prepareAllContent();
    applyState({ playContent: true, contentDelay: 0.15 });

    if (nextBtn) nextBtn.addEventListener("click", () => animateNext(false));
    if (prevBtn) prevBtn.addEventListener("click", animatePrev);

    window.addEventListener("resize", () => {
      applyState({ playContent: false });
    });
  });
}

document.addEventListener("DOMContentLoaded", function () {
  initDroppingCardsStack();
});
````
