Great Tech Teams Don't Create Themselves. We'll Show You How.

Navigate the ever-evolving IT landscape with expert insights, strategies, and articles on building and scaling top-tier tech teams.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
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
How EdTech CTOs Use the Staff Augmentation Process to Accelerate Delivery

How EdTech CTOs Use the Staff Augmentation Process to Accelerate Delivery

Learn more
Team using online learning tools with AI-driven personalization, highlighting how EdTech customizes course materials, tracks student performance, and improves outcomes.
Top Trends in Educational Technology Shaping the Future of Digital Learning

EdTech is entering an era in which innovation meets urgency. From AI-driven personalization to immersive and adaptive tools, discover the trends shaping digital learning in 2025 and how forward-thinking teams are using them to scale smarter.

Learn more