# Cascading Slider

> Stacked slides that cascade in sequence with refined depth — a polished carousel for featured content.

**Definition:** Cascading Slider is a sliders 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: Sliders
- Stack: HTML, CSS, JavaScript, and GSAP
- Added: 2026-08-01
- Canonical page: https://html.bosconvertpro.com/collection/cascading-slider
- Full-page preview: https://html.bosconvertpro.com/collection/cascading-slider/full
- Live demo: https://html.bosconvertpro.com/demos/cascading-slider/index.html
- Markdown mirror: https://html.bosconvertpro.com/collection/cascading-slider/index.md
- License: https://html.bosconvertpro.com/terms

## How to use
1. Open https://html.bosconvertpro.com/collection/cascading-slider/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/cascading-slider.
3. Paste the files into your project and adjust timing, easing, and content as needed.

## FAQ
### What is Cascading Slider?

Cascading Slider is a sliders UI animation from collections.html. Stacked slides that cascade in sequence with refined depth — a polished carousel for featured content.

### What is Cascading Slider built with?

Cascading Slider 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 Cascading Slider in my project?

Open the animation page (https://html.bosconvertpro.com/collection/cascading-slider), 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/cascading-slider/index.html.

### Is Cascading Slider free to use?

Yes. The Cascading Slider 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>Cascading Slider</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&display=swap"
      rel="stylesheet"
    />
    <link rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <main class="app">
      <div
        data-cascading-slider-wrap
        class="cascading-slider"
        aria-label="Featured content"
        aria-roledescription="carousel"
      >
        <div class="cascading-slider__collection">
          <div data-cascading-viewport class="cascading-slider__list">
            <div
              aria-roledescription="slide"
              data-cascading-slide
              role="group"
              class="cascading-slider__item"
            >
              <div class="cascading-slider__item-inner">
                <div class="cascading-slider__item-bg">
                  <img
                    src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?auto=format&fit=crop&w=1600&q=85"
                    alt="Snow-capped mountain range under soft clouds"
                    loading="eager"
                    draggable="false"
                    class="cascading-slider__img"
                  />
                </div>
                <div class="cascading-slider__item-content">
                  <h3 class="cascading-slider__h">Alpine Horizons</h3>
                </div>
              </div>
            </div>

            <div
              aria-roledescription="slide"
              data-cascading-slide
              role="group"
              class="cascading-slider__item"
            >
              <div class="cascading-slider__item-inner">
                <div class="cascading-slider__item-bg">
                  <img
                    src="https://images.unsplash.com/photo-1441974231531-c6227db76b6e?auto=format&fit=crop&w=1600&q=85"
                    alt="Sunlit green forest path through tall trees"
                    loading="lazy"
                    draggable="false"
                    class="cascading-slider__img"
                  />
                </div>
                <div class="cascading-slider__item-content">
                  <h3 class="cascading-slider__h">Forest Canopy</h3>
                </div>
              </div>
            </div>

            <div
              aria-roledescription="slide"
              data-cascading-slide
              role="group"
              class="cascading-slider__item"
            >
              <div class="cascading-slider__item-inner">
                <div class="cascading-slider__item-bg">
                  <img
                    src="https://images.unsplash.com/photo-1497366216548-37526070297c?auto=format&fit=crop&w=1600&q=85"
                    alt="Bright modern office interior with glass walls"
                    loading="lazy"
                    draggable="false"
                    class="cascading-slider__img"
                  />
                </div>
                <div class="cascading-slider__item-content">
                  <h3 class="cascading-slider__h">Modern Workspace</h3>
                </div>
              </div>
            </div>

            <div
              aria-roledescription="slide"
              data-cascading-slide
              role="group"
              class="cascading-slider__item"
            >
              <div class="cascading-slider__item-inner">
                <div class="cascading-slider__item-bg">
                  <img
                    src="https://images.unsplash.com/photo-1514565131-fce0801e5785?auto=format&fit=crop&w=1600&q=85"
                    alt="City skyline glowing at night with traffic lights"
                    loading="lazy"
                    draggable="false"
                    class="cascading-slider__img"
                  />
                </div>
                <div class="cascading-slider__item-content">
                  <h3 class="cascading-slider__h">City Nights</h3>
                </div>
              </div>
            </div>

            <div
              aria-roledescription="slide"
              data-cascading-slide
              role="group"
              class="cascading-slider__item"
            >
              <div class="cascading-slider__item-inner">
                <div class="cascading-slider__item-bg">
                  <img
                    src="https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?auto=format&fit=crop&w=1600&q=85"
                    alt="Glass skyscraper architecture rising into the sky"
                    loading="lazy"
                    draggable="false"
                    class="cascading-slider__img"
                  />
                </div>
                <div class="cascading-slider__item-content">
                  <h3 class="cascading-slider__h">Glass Towers</h3>
                </div>
              </div>
            </div>
          </div>
        </div>

        <nav aria-label="Slider navigation" class="cascading-slider__nav">
          <div class="cascading-slider__controls">
            <button
              data-cascading-slider-prev
              type="button"
              aria-label="Previous slide"
              class="cascading-slider__button"
            >
              <span class="cascading-slider__button-ring" aria-hidden="true"></span>
              <span class="cascading-slider__button-glow" aria-hidden="true"></span>
              <span class="cascading-slider__button-shine" aria-hidden="true"></span>
              <svg
                xmlns="http://www.w3.org/2000/svg"
                viewBox="0 0 24 24"
                fill="none"
                class="cascading-slider__button-icon"
                aria-hidden="true"
              >
                <path
                  d="M14.5 6.5 9 12l5.5 5.5"
                  stroke="currentColor"
                  stroke-width="1.75"
                  stroke-linecap="round"
                  stroke-linejoin="round"
                />
                <path
                  class="cascading-slider__button-icon-trail"
                  d="M15 12H9.25"
                  stroke="currentColor"
                  stroke-width="1.75"
                  stroke-linecap="round"
                />
              </svg>
            </button>

            <div class="cascading-slider__controls-divider" aria-hidden="true"></div>

            <button
              data-cascading-slider-next
              type="button"
              aria-label="Next slide"
              class="cascading-slider__button"
            >
              <span class="cascading-slider__button-ring" aria-hidden="true"></span>
              <span class="cascading-slider__button-glow" aria-hidden="true"></span>
              <span class="cascading-slider__button-shine" aria-hidden="true"></span>
              <svg
                xmlns="http://www.w3.org/2000/svg"
                viewBox="0 0 24 24"
                fill="none"
                class="cascading-slider__button-icon"
                aria-hidden="true"
              >
                <path
                  d="M9.5 6.5 15 12l-5.5 5.5"
                  stroke="currentColor"
                  stroke-width="1.75"
                  stroke-linecap="round"
                  stroke-linejoin="round"
                />
                <path
                  class="cascading-slider__button-icon-trail"
                  d="M9 12h5.75"
                  stroke="currentColor"
                  stroke-width="1.75"
                  stroke-linecap="round"
                />
              </svg>
            </button>
          </div>
        </nav>
      </div>
    </main>

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

## Source — styles.css
````css
:root {
  --bg: #0f1412;
  --bg-mid: #1a2420;
  --bg-deep: #0a0e0c;
  --accent: #c8e6c9;
  --accent-soft: #a8d5b5;
  --accent-gold: #d4c4a8;
  --ink: #1a221c;
  --ink-soft: #2c3830;
  --white: #ffffff;
  --gap: 0.5em;
  --radius: 0.75em;
  --ease-out: cubic-bezier(0.645, 0.045, 0.355, 1);
  --ease-premium: cubic-bezier(0.22, 1, 0.36, 1);
  --font: "Inter", system-ui, -apple-system, sans-serif;
  --glass: rgba(255, 255, 255, 0.08);
  --glass-border: rgba(255, 255, 255, 0.14);
  --shadow-btn:
    0 1px 0 rgba(255, 255, 255, 0.65) inset,
    0 -2px 6px rgba(0, 0, 0, 0.08) inset,
    0 10px 28px rgba(0, 0, 0, 0.28),
    0 2px 8px rgba(0, 0, 0, 0.16),
    0 0 0 1px rgba(255, 255, 255, 0.12);
  --shadow-btn-hover:
    0 1px 0 rgba(255, 255, 255, 0.85) inset,
    0 -2px 6px rgba(0, 0, 0, 0.06) inset,
    0 16px 40px rgba(0, 0, 0, 0.32),
    0 6px 16px rgba(168, 213, 181, 0.18),
    0 0 0 1px rgba(255, 255, 255, 0.22);
  --shadow-btn-active:
    0 1px 0 rgba(255, 255, 255, 0.4) inset,
    0 4px 14px rgba(0, 0, 0, 0.22);
}

*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html,
body {
  width: 100%;
  height: 100%;
}

body {
  font-family: var(--font);
  color: var(--white);
  background: var(--bg);
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

button {
  font: inherit;
  border: 0;
  background: none;
  color: inherit;
  cursor: pointer;
}

img {
  display: block;
  max-width: 100%;
}

/* ---------- App shell ---------- */

.app {
  width: 100vw;
  height: 100%;
  height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(1.25rem, 3vw, 2.5rem);
  position: relative;
  overflow: hidden;
  background:
    radial-gradient(ellipse 70% 55% at 18% 20%, rgba(90, 140, 110, 0.22) 0%, transparent 55%),
    radial-gradient(ellipse 55% 50% at 82% 75%, rgba(180, 150, 100, 0.12) 0%, transparent 50%),
    radial-gradient(ellipse 90% 70% at 50% 40%, rgba(40, 70, 55, 0.45) 0%, transparent 62%),
    radial-gradient(circle at 50% 100%, rgba(20, 35, 28, 0.9) 0%, transparent 45%),
    linear-gradient(
      155deg,
      #1c2a24 0%,
      #141c18 28%,
      #0f1412 55%,
      #0a100e 78%,
      #080c0a 100%
    );
}

.app::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(ellipse 40% 30% at 50% 0%, rgba(200, 230, 201, 0.06) 0%, transparent 70%),
    linear-gradient(
      180deg,
      rgba(255, 255, 255, 0.03) 0%,
      transparent 30%,
      transparent 70%,
      rgba(0, 0, 0, 0.25) 100%
    );
  z-index: 0;
}

.app::after {
  content: "";
  position: absolute;
  width: min(70vw, 720px);
  height: min(70vw, 720px);
  left: 50%;
  top: 42%;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: radial-gradient(
    circle,
    rgba(168, 213, 181, 0.07) 0%,
    rgba(100, 140, 115, 0.03) 40%,
    transparent 70%
  );
  filter: blur(40px);
  pointer-events: none;
  z-index: 0;
}

/* ---------- Slider ---------- */

[data-cascading-viewport] {
  --gap: 0.5em;
}

[data-cascading-slide] {
  --clip: 0;
  --radius: 0.75em;
}

.cascading-slider {
  width: 100%;
  max-width: 90em;
  margin-left: auto;
  margin-right: auto;
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: stretch;
}

.cascading-slider__collection {
  width: 100%;
}

.cascading-slider__list {
  width: 100%;
  height: min(35em, 62vh);
  position: relative;
  overflow: hidden;
}

.cascading-slider__item {
  color: var(--white);
  cursor: pointer;
  will-change: transform, clip-path;
  clip-path: inset(0px calc(var(--clip) * 1px) round var(--radius));
  -webkit-user-select: none;
  user-select: none;
  height: 100%;
  position: absolute;
  inset: 0% auto auto 0%;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}

.cascading-slider__item[data-status="active"] {
  cursor: default;
}

.cascading-slider__item-inner {
  width: 100%;
  height: 100%;
  position: relative;
  overflow: hidden;
  border-radius: var(--radius);
}

.cascading-slider__item-bg {
  z-index: 0;
  position: absolute;
  inset: 0%;
}

.cascading-slider__img {
  object-fit: cover;
  width: 100%;
  height: 100%;
  position: absolute;
  inset: 0%;
  pointer-events: none;
  -webkit-user-drag: none;
}

.cascading-slider__item-content {
  z-index: 2;
  background-image: linear-gradient(0deg, rgba(0, 0, 0, 0.55), transparent);
  padding: 2em 2em 3em 2.5em;
  position: absolute;
  inset: auto 0% 0%;
  pointer-events: none;
}

.cascading-slider__h {
  opacity: 0;
  letter-spacing: -0.03em;
  margin: 0;
  font-family: var(--font);
  font-size: clamp(1.35rem, 2.4vw, 2.75em);
  font-weight: 400;
  line-height: 1;
  transition: opacity 0.3s var(--ease-out), transform 0.3s var(--ease-out);
  transform: translate(0, 0.25em);
  transition-delay: 0ms;
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.25);
}

[data-cascading-slide][data-status="active"] .cascading-slider__h {
  transition-delay: 400ms;
  opacity: 1;
  transform: translate(0, 0);
}

/* ---------- Premium controls (bottom center) ---------- */

.cascading-slider__nav {
  display: flex;
  flex-flow: row;
  justify-content: center;
  align-items: center;
  margin-top: clamp(2rem, 4.5vw, 3.75rem);
  margin-left: auto;
  margin-right: auto;
  position: relative;
  z-index: 20;
}

.cascading-slider__controls {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.4rem;
  border-radius: 999px;
  background:
    linear-gradient(
      145deg,
      rgba(255, 255, 255, 0.1) 0%,
      rgba(255, 255, 255, 0.04) 45%,
      rgba(0, 0, 0, 0.12) 100%
    );
  border: 1px solid var(--glass-border);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.1) inset,
    0 16px 48px rgba(0, 0, 0, 0.35),
    0 4px 16px rgba(0, 0, 0, 0.2),
    0 0 0 1px rgba(0, 0, 0, 0.2);
  backdrop-filter: blur(18px) saturate(1.35);
  -webkit-backdrop-filter: blur(18px) saturate(1.35);
}

.cascading-slider__controls-divider {
  width: 1px;
  height: 1.35rem;
  margin: 0 0.2rem;
  background: linear-gradient(
    180deg,
    transparent 0%,
    rgba(255, 255, 255, 0.22) 40%,
    rgba(255, 255, 255, 0.22) 60%,
    transparent 100%
  );
  opacity: 0.7;
}

.cascading-slider__button {
  position: relative;
  color: var(--ink);
  width: 3.4em;
  height: 3.4em;
  padding: 0;
  border-radius: 50%;
  display: flex;
  justify-content: center;
  align-items: center;
  overflow: hidden;
  isolation: isolate;
  background:
    linear-gradient(160deg, #f4faf4 0%, #dceee0 38%, #c5dfc9 72%, #b4d4ba 100%);
  box-shadow: var(--shadow-btn);
  border: 1px solid rgba(255, 255, 255, 0.45);
  transition:
    transform 0.4s var(--ease-premium),
    box-shadow 0.4s var(--ease-premium),
    background 0.4s var(--ease-premium),
    color 0.25s ease;
  -webkit-tap-highlight-color: transparent;
}

.cascading-slider__button-ring {
  position: absolute;
  inset: 2px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.35);
  pointer-events: none;
  z-index: 2;
  opacity: 0.55;
  transition: opacity 0.35s var(--ease-premium), border-color 0.35s ease;
}

.cascading-slider__button-glow {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: radial-gradient(
    120% 100% at 32% 18%,
    rgba(255, 255, 255, 0.7) 0%,
    rgba(255, 255, 255, 0.15) 42%,
    transparent 68%
  );
  opacity: 0.9;
  pointer-events: none;
  z-index: 0;
  transition: opacity 0.35s var(--ease-premium);
}

.cascading-slider__button-shine {
  position: absolute;
  top: -40%;
  left: -30%;
  width: 70%;
  height: 70%;
  border-radius: 50%;
  background: radial-gradient(
    circle,
    rgba(255, 255, 255, 0.55) 0%,
    transparent 70%
  );
  pointer-events: none;
  z-index: 1;
  opacity: 0.75;
  transition: opacity 0.35s var(--ease-premium), transform 0.45s var(--ease-premium);
}

.cascading-slider__button-icon {
  position: relative;
  z-index: 3;
  width: 1.35rem;
  height: 1.35rem;
  display: block;
  transition: transform 0.4s var(--ease-premium);
}

.cascading-slider__button-icon-trail {
  opacity: 0;
  stroke-dasharray: 8;
  stroke-dashoffset: 8;
  transition:
    opacity 0.3s var(--ease-premium),
    stroke-dashoffset 0.4s var(--ease-premium);
}

.cascading-slider__button:hover {
  color: var(--ink-soft);
  background:
    linear-gradient(160deg, #ffffff 0%, #e8f5ea 36%, #d0e8d4 70%, #c0ddc6 100%);
  box-shadow: var(--shadow-btn-hover);
  transform: translateY(-3px) scale(1.04);
}

.cascading-slider__button:hover .cascading-slider__button-glow {
  opacity: 1;
}

.cascading-slider__button:hover .cascading-slider__button-shine {
  opacity: 1;
  transform: translate(4px, 4px) scale(1.05);
}

.cascading-slider__button:hover .cascading-slider__button-ring {
  opacity: 0.9;
  border-color: rgba(255, 255, 255, 0.55);
}

.cascading-slider__button:hover .cascading-slider__button-icon {
  transform: scale(1.06);
}

.cascading-slider__button:hover .cascading-slider__button-icon-trail {
  opacity: 0.9;
  stroke-dashoffset: 0;
}

.cascading-slider__button[data-cascading-slider-prev]:hover .cascading-slider__button-icon {
  transform: translateX(-2px) scale(1.06);
}

.cascading-slider__button[data-cascading-slider-next]:hover .cascading-slider__button-icon {
  transform: translateX(2px) scale(1.06);
}

.cascading-slider__button:active {
  transform: translateY(0) scale(0.96);
  box-shadow: var(--shadow-btn-active);
}

.cascading-slider__button:focus-visible {
  outline: 2px solid rgba(200, 230, 201, 0.85);
  outline-offset: 4px;
}

.cascading-slider__button.is-disabled {
  opacity: 0.45;
  pointer-events: none;
}

/* ---------- Responsive ---------- */

@media (max-width: 991px) {
  .cascading-slider__list {
    height: min(28em, 52vh);
  }

  .cascading-slider__item-content {
    padding: 1.5em 1.5em 2em 1.75em;
  }
}

@media (max-width: 767px) {
  .app {
    padding: 1rem;
  }

  .cascading-slider__list {
    height: min(22em, 48vh);
  }

  .cascading-slider__item-content {
    padding: 1.15em 1.15em 1.5em 1.25em;
  }

  .cascading-slider__nav {
    margin-top: 1.75rem;
  }

  .cascading-slider__controls {
    padding: 0.32rem;
    gap: 0.25rem;
  }

  .cascading-slider__button {
    width: 3em;
    height: 3em;
  }

  .cascading-slider__button-icon {
    width: 1.2rem;
    height: 1.2rem;
  }
}

@media (max-width: 479px) {
  .cascading-slider__list {
    height: min(18em, 42vh);
  }

  .cascading-slider__h {
    font-size: 1.2rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  .cascading-slider__h,
  .cascading-slider__button,
  .cascading-slider__button-icon,
  .cascading-slider__button-glow,
  .cascading-slider__button-shine,
  .cascading-slider__button-ring,
  .cascading-slider__button-icon-trail {
    transition-duration: 0.01ms !important;
  }
}
````

## Source — script.js
````javascript
function initCascadingSlider() {
  const duration = 0.65;
  const ease = "power3.inOut";

  const breakpoints = [
    { maxWidth: 479, activeWidth: 0.78, siblingWidth: 0.08 },
    { maxWidth: 767, activeWidth: 0.7, siblingWidth: 0.1 },
    { maxWidth: 991, activeWidth: 0.6, siblingWidth: 0.1 },
    { maxWidth: Infinity, activeWidth: 0.6, siblingWidth: 0.13 },
  ];

  const wrappers = document.querySelectorAll("[data-cascading-slider-wrap]");
  wrappers.forEach(setupInstance);

  function setupInstance(wrapper) {
    const viewport = wrapper.querySelector("[data-cascading-viewport]");
    const prevButton = wrapper.querySelector("[data-cascading-slider-prev]");
    const nextButton = wrapper.querySelector("[data-cascading-slider-next]");
    const slides = Array.from(
      viewport.querySelectorAll("[data-cascading-slide]")
    );

    let totalSlides = slides.length;
    if (totalSlides === 0) return;

    // Ensure enough slides for a full cascade loop
    if (totalSlides < 9) {
      const originalSlides = slides.slice();
      while (slides.length < 9) {
        originalSlides.forEach(function (original) {
          const clone = original.cloneNode(true);
          clone.setAttribute("data-clone", "");
          viewport.appendChild(clone);
          slides.push(clone);
        });
      }
      totalSlides = slides.length;
    }

    let activeIndex = 0;
    let isAnimating = false;
    let slideWidth = 0;
    let slotCenters = {};
    let slotWidths = {};

    function readGap() {
      const raw = getComputedStyle(viewport).getPropertyValue("--gap").trim();
      if (!raw) return 0;
      const temp = document.createElement("div");
      temp.style.width = raw;
      temp.style.position = "absolute";
      temp.style.visibility = "hidden";
      viewport.appendChild(temp);
      const px = temp.offsetWidth;
      viewport.removeChild(temp);
      return px;
    }

    function getSettings() {
      const windowWidth = window.innerWidth;
      for (let i = 0; i < breakpoints.length; i++) {
        if (windowWidth <= breakpoints[i].maxWidth) return breakpoints[i];
      }
      return breakpoints[breakpoints.length - 1];
    }

    function getOffset(slideIndex, fromIndex) {
      if (fromIndex === undefined) fromIndex = activeIndex;
      let distance = slideIndex - fromIndex;
      const half = totalSlides / 2;
      if (distance > half) distance -= totalSlides;
      if (distance < -half) distance += totalSlides;
      return distance;
    }

    function measure() {
      const settings = getSettings();
      const viewportWidth = viewport.offsetWidth;
      const gap = readGap();
      const activeSlideWidth = viewportWidth * settings.activeWidth;
      const siblingSlideWidth = viewportWidth * settings.siblingWidth;
      const farSlideWidth = Math.max(
        0,
        (viewportWidth - activeSlideWidth - 2 * siblingSlideWidth - 4 * gap) / 2
      );

      slideWidth = activeSlideWidth;

      const visibleSlots = [
        { slot: -2, width: farSlideWidth },
        { slot: -1, width: siblingSlideWidth },
        { slot: 0, width: activeSlideWidth },
        { slot: 1, width: siblingSlideWidth },
        { slot: 2, width: farSlideWidth },
      ];

      let x = 0;
      visibleSlots.forEach(function (def, i) {
        slotCenters[String(def.slot)] = x + def.width / 2;
        slotWidths[String(def.slot)] = def.width;
        if (i < visibleSlots.length - 1) x += def.width + gap;
      });

      // Staging slots just off the visible edges
      slotCenters["-3"] =
        slotCenters["-2"] - farSlideWidth / 2 - gap - farSlideWidth / 2;
      slotWidths["-3"] = farSlideWidth;
      slotCenters["3"] =
        slotCenters["2"] + farSlideWidth / 2 + gap + farSlideWidth / 2;
      slotWidths["3"] = farSlideWidth;

      slides.forEach(function (slide) {
        slide.style.width = slideWidth + "px";
      });
    }

    function getSlideProps(offset) {
      const clamped = Math.max(-3, Math.min(3, offset));
      const slotWidth = slotWidths[String(clamped)];
      const clipAmount = Math.max(0, (slideWidth - slotWidth) / 2);
      const translateX = slotCenters[String(clamped)] - slideWidth / 2;

      return {
        x: translateX,
        "--clip": clipAmount,
        zIndex: 10 - Math.abs(clamped),
      };
    }

    function layout(animate, previousIndex) {
      slides.forEach(function (slide, index) {
        const offset = getOffset(index);

        if (offset < -3 || offset > 3) {
          if (animate && previousIndex !== undefined) {
            const previousOffset = getOffset(index, previousIndex);
            if (previousOffset >= -2 && previousOffset <= 2) {
              const exitSlot = previousOffset < 0 ? -3 : 3;
              gsap.to(
                slide,
                Object.assign({}, getSlideProps(exitSlot), {
                  duration: duration,
                  ease: ease,
                  overwrite: true,
                })
              );
              return;
            }
          }
          const parkSlot = offset < 0 ? -3 : 3;
          gsap.set(slide, getSlideProps(parkSlot));
          return;
        }

        const props = getSlideProps(offset);
        slide.setAttribute(
          "data-status",
          offset === 0 ? "active" : "inactive"
        );

        if (animate) {
          gsap.to(
            slide,
            Object.assign({}, props, {
              duration: duration,
              ease: ease,
              overwrite: true,
            })
          );
        } else {
          gsap.set(slide, props);
        }
      });
    }

    function goTo(targetIndex) {
      const normalizedTarget =
        ((targetIndex % totalSlides) + totalSlides) % totalSlides;
      if (isAnimating || normalizedTarget === activeIndex) return;

      isAnimating = true;
      const previousIndex = activeIndex;
      const travelDirection =
        getOffset(normalizedTarget, previousIndex) > 0 ? 1 : -1;

      slides.forEach(function (slide, index) {
        const currentOffset = getOffset(index, previousIndex);
        const nextOffset = getOffset(index, normalizedTarget);
        const wasInRange = currentOffset >= -3 && currentOffset <= 3;
        const willBeVisible = nextOffset >= -2 && nextOffset <= 2;

        if (!wasInRange && willBeVisible) {
          const entrySlot = travelDirection > 0 ? 3 : -3;
          gsap.set(slide, getSlideProps(entrySlot));
        }

        const wasInvisible = Math.abs(currentOffset) >= 3;
        const willBeStaging = Math.abs(nextOffset) === 3;
        const crossesSides = currentOffset * nextOffset < 0;

        if (wasInvisible && willBeStaging && crossesSides) {
          gsap.set(slide, getSlideProps(nextOffset > 0 ? 3 : -3));
        }
      });

      activeIndex = normalizedTarget;
      layout(true, previousIndex);
      gsap.delayedCall(duration + 0.05, function () {
        isAnimating = false;
      });
    }

    if (prevButton) {
      prevButton.addEventListener("click", function () {
        goTo(activeIndex - 1);
      });
    }

    if (nextButton) {
      nextButton.addEventListener("click", function () {
        goTo(activeIndex + 1);
      });
    }

    slides.forEach(function (slide, index) {
      slide.addEventListener("click", function () {
        if (index !== activeIndex) goTo(index);
      });
    });

    // Keyboard
    document.addEventListener("keydown", function (event) {
      if (event.key === "ArrowLeft") goTo(activeIndex - 1);
      if (event.key === "ArrowRight") goTo(activeIndex + 1);
    });

    // Touch / drag swipe
    let pointerStartX = null;
    let pointerActive = false;

    viewport.addEventListener(
      "pointerdown",
      function (event) {
        if (event.button !== 0 && event.pointerType === "mouse") return;
        pointerActive = true;
        pointerStartX = event.clientX;
        viewport.setPointerCapture?.(event.pointerId);
      },
      { passive: true }
    );

    viewport.addEventListener(
      "pointerup",
      function (event) {
        if (!pointerActive || pointerStartX === null) return;
        const delta = event.clientX - pointerStartX;
        pointerActive = false;
        pointerStartX = null;
        const threshold = 40;
        if (Math.abs(delta) < threshold) return;
        if (delta < 0) goTo(activeIndex + 1);
        else goTo(activeIndex - 1);
      },
      { passive: true }
    );

    viewport.addEventListener(
      "pointercancel",
      function () {
        pointerActive = false;
        pointerStartX = null;
      },
      { passive: true }
    );

    // Resize
    let resizeTimer;
    window.addEventListener("resize", function () {
      clearTimeout(resizeTimer);
      resizeTimer = setTimeout(function () {
        measure();
        layout(false);
      }, 100);
    });

    measure();
    layout(false);
  }
}

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