Near Future

Unveiling Tomorrow's Innovations, Transforming Today's Dreams

1. CDN Scripts

Add these to your <head> or before closing </body> tag:

<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/Flip.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/CustomEase.min.js"></script>

2. Required CSS

[data-flip-gallery] {
  width: 100vw;
  position: relative;
}

[data-flip-container] {
  width: 100%;
  padding-top: 100vh;
}

[data-flip-item] {
  position: relative;
  margin-bottom: 1em;
  width: 400px;
  height: 500px;
  overflow: hidden;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

[data-flip-item]:nth-child(odd) {
  left: 75%;
}

[data-flip-item]:nth-child(even) {
  left: 25%;
}

[data-flip-item].is-stacked {
  position: absolute;
  top: 47.5%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 300px;
  height: 400px;
}

[data-flip-trigger] {
  position: absolute;
  top: 75%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: #0084ff;
  color: #fff;
  font-family: inherit;
  font-size: 12px;
  border-radius: 30px;
  padding: 1em 2em;
  cursor: pointer;
  z-index: 2;
  border: none;
}

3. HTML Usage

<!-- Gallery wrapper with config -->
<div data-flip-gallery data-flip-duration="2" data-flip-stagger="0.05">
  <div data-flip-container class="is-stacked">

    <!-- Each item: rotation value controls the stacked fan angle -->
    <div data-flip-item class="is-stacked" data-flip-rotation="10">
      <img src="image1.jpg" />
    </div>
    <div data-flip-item class="is-stacked" data-flip-rotation="-5">
      <img src="image2.jpg" />
    </div>
    <div data-flip-item class="is-stacked" data-flip-rotation="2">
      <img src="image3.jpg" />
    </div>
    <div data-flip-item class="is-stacked" data-flip-rotation="-2">
      <img src="image4.jpg" />
    </div>

  </div>
</div>

<!-- Trigger button -->
<button
  data-flip-trigger
  data-flip-text-expand="Explore Ideas"
  data-flip-text-collapse="Hide All Ideas"
>
  Explore Ideas
</button>

4. JavaScript

gsap.registerPlugin(Flip);
CustomEase.create("cubic", "0.83, 0, 0.17, 1");

document.addEventListener("DOMContentLoaded", () => {
  document.querySelectorAll("[data-flip-gallery]").forEach((gallery) => {
    const container = gallery.querySelector("[data-flip-container]");
    const items = gsap.utils.toArray(
      gallery.querySelectorAll("[data-flip-item]")
    );
    const duration = parseFloat(gallery.dataset.flipDuration) || 2;
    const stagger = parseFloat(gallery.dataset.flipStagger) || 0.05;

    const trigger = document.querySelector("[data-flip-trigger]");
    const expandText = trigger?.dataset.flipTextExpand || "Explore Ideas";
    const collapseText = trigger?.dataset.flipTextCollapse || "Hide All Ideas";

    let isStacked = container.classList.contains("is-stacked");

    // Apply initial rotation from data attributes
    if (isStacked) {
      items.forEach((item) => {
        const rotation = parseFloat(item.dataset.flipRotation) || 0;
        gsap.set(item, { rotate: rotation });
      });
    }

    function applyRotation() {
      items.forEach((item) => {
        const rotation = isStacked
          ? parseFloat(item.dataset.flipRotation) || 0
          : 0;
        gsap.to(item, {
          rotate: rotation,
          duration: duration,
          ease: "cubic",
          delay: 0.15,
        });
      });
    }

    trigger?.addEventListener("click", () => {
      isStacked = !isStacked;

      setTimeout(() => {
        trigger.textContent = isStacked ? expandText : collapseText;
      }, 1000);

      const state = Flip.getState(
        "[data-flip-container], [data-flip-item]"
      );

      container.classList.toggle("is-stacked");
      items.forEach((item) => item.classList.toggle("is-stacked"));

      Flip.from(state, {
        absolute: true,
        duration: duration,
        rotate: 0,
        stagger: stagger,
        ease: "cubic",
        onStart: () => applyRotation(),
      });
    });
  });
});

5. Data Attributes Reference

Attribute On Default Description
data-flip-gallery Gallery wrapper required Enables the flip gallery on this element
data-flip-container Inner container required Direct child that holds all items
data-flip-item Each gallery item required Marks an element as a flip-animated item
data-flip-trigger Button required Clicking this toggles stacked/spread view
data-flip-duration Gallery wrapper 2 Animation duration in seconds
data-flip-stagger Gallery wrapper 0.05 Delay between each item's animation
data-flip-rotation Each item 0 Rotation angle (degrees) in stacked view
data-flip-text-expand Trigger button Explore Ideas Button text when gallery is stacked
data-flip-text-collapse Trigger button Hide All Ideas Button text when gallery is spread

6. How It Works

The animation relies on two CSS states controlled by the is-stacked class:

Stacked state (initial) — items are position: absolute, centered and overlapping with individual rotation values creating a fanned card pile effect.

Spread state (toggled) — items are position: relative, laid out vertically with alternating left/right positions using nth-child.

GSAP Flip captures the element positions before the class toggle, then animates from the old positions to the new ones. The CustomEase with cubic values (0.83, 0, 0.17, 1) creates a smooth, snappy transition.

7. Customization

<!-- Faster animation with more stagger -->
<div data-flip-gallery data-flip-duration="1.2" data-flip-stagger="0.12">
  ...
</div>

<!-- Dramatic rotation values -->
<div data-flip-item class="is-stacked" data-flip-rotation="25">...</div>
<div data-flip-item class="is-stacked" data-flip-rotation="-15">...</div>

<!-- Custom button labels -->
<button
  data-flip-trigger
  data-flip-text-expand="Show Gallery"
  data-flip-text-collapse="Close Gallery"
>
  Show Gallery
</button>