var Webflow = Webflow || []
Webflow.push(function () {
const draggableCardsWrappers = document.querySelectorAll('[fc-draggable-card = wrapper]')
for(const wrapper of draggableCardsWrappers) {
let draggableCards = Array.from(wrapper.querySelectorAll('[fc-draggable-card = component]'))
// Initial setup on page load — target the top card
if (draggableCards.length) {
const topCard = draggableCards[draggableCards.length - 1]
addHoverEffect(topCard)
initDraggable(topCard, draggableCards)
}
// Reset button logic: clear all styles and reinitialize top card
wrapper.querySelector('[fc-draggable-card = reset]').addEventListener('click', () => {
draggableCards = Array.from(wrapper.querySelectorAll('[fc-draggable-card = component]'))
draggableCards.forEach(card => {
// Remove all inline styles applied by GSAP
gsap.set(card, {
clearProps: "all"
})
})
if (draggableCards.length) {
const topCard = draggableCards[draggableCards.length - 1]
addHoverEffect(topCard)
initDraggable(topCard, draggableCards)
}
})
}
})
// Helper: get a numeric attribute from an element, with fallback if missing or invalid
function getAttr(el, attr, fallback) {
const val = el.getAttribute(attr)
const num = parseFloat(val)
return !isNaN(num) ? num : fallback
}
// Helper: get a string attribute from an element, with fallback if missing
function getStringAttr(el, attr, fallback) {
const val = el.getAttribute(attr)
return val && typeof val === "string" ? val : fallback
}
// Adds a hover animation to the topmost card — used only once on load or reset
function addHoverEffect(card) {
card.addEventListener('mouseenter', () => {
gsap.to(card, {
rotation: 10,
x: '30%',
duration: 0.3,
ease: 'power2.out'
})
})
card.addEventListener('mouseleave', () => {
gsap.to(card, {
rotation: 0,
x: 0,
duration: 0.3,
ease: 'power2.out'
})
})
}
// Initializes GSAP Draggable for a given card
function initDraggable(card, draggableCards) {
// Extract configuration from attributes with fallbacks
const rotation = getAttr(card, 'fc-draggable-card-rotation', 45)
const resetDuration = getAttr(card, 'fc-draggable-card-reset-duration', 0.2)
const throwDuration = getAttr(card, 'fc-draggable-card-throw-duration', 0.5)
const throwDistance = getAttr(card, 'fc-draggable-card-throw-distance', 1000)
const throwRotation = getAttr(card, 'fc-draggable-card-throw-rotation', 45)
const threshold = getAttr(card, 'fc-draggable-card-threshold', 100)
const delayBeforeNext = getAttr(card, 'fc-draggable-card-delay', 0)
const easingFunctionRaw = getStringAttr(card, 'fc-draggable-card-ease', 'power4.out')
let easingFunction
try {
easingFunction = gsap.parseEase(easingFunctionRaw)
} catch (e) {
easingFunction = 'power4.out'
}
let isReleased = false
// Ensure no duplicate Draggable instance is attached
Draggable.get(card)?.kill()
Draggable.create(card, {
type: "x",
// On press: kill any hover animation in progress
onPress() {
gsap.killTweensOf(card)
},
// On drag: update rotation dynamically, with configured easing
onDrag() {
if (isReleased) return
gsap.to(card, {
rotation: this.x / rotation,
ease: easingFunction,
duration: 0.2,
overwrite: true
})
},
// On release: handle reset or throw based on threshold
onRelease() {
const nextCard = draggableCards[draggableCards.indexOf(card) - 1]
if (Math.abs(this.x) < threshold) {
// If under threshold, reset to initial position
gsap.to(card, {
x: 0,
opacity: 1,
duration: resetDuration,
ease: easingFunction,
overwrite: true,
onComplete: () => {
isReleased = false
}
})
} else {
// If dragged past threshold, animate out and initialize next card
const direction = this.x > 0 ? 1 : -1
gsap.to(card, {
x: direction * throwDistance,
rotation: direction * throwRotation,
opacity: 0,
duration: throwDuration,
ease: easingFunction,
onComplete: () => {
card.style.display = 'none'
setTimeout(() => {
// Find next visible card from the top of the stack
const next = draggableCards.slice().reverse().find(card => card.style.display !== 'none')
if (next) initDraggable(next, draggableCards)
}, delayBeforeNext)
}
})
}
}
})
}

Case Study
Empowering EdTech Innovation with Tailored Talent Solutions
This case study showcases how Genius Match helped a leading EdTech company fast-track its digital transformation and product roadmap through tailored outstaffing solutions.
Learn more