Skip to content

[FEAT]: components/bounce-cards #703

@Visheshj111

Description

@Visheshj111

Share your suggestion

Hey! I used your bounce cards component and made some improvements that really enhanced the experience. Here's what I changed:

1. Fixed the Alignment Bug
Your original code had CSS transforms fighting with GSAP transforms, which caused cards to drift vertically (especially noticeable on the JavaScript card). I fixed this by:

Removing all CSS positioning (top, left, transform) from the card classes
Letting GSAP handle everything using xPercent: -50, yPercent: -50 for centering
Setting all cards to y: 0 so they sit on the same baseline - this was the key!
The Problem: CSS was applying translate(-50%, -50%) and then GSAP was applying its own transforms on top, creating a compound effect that misaligned cards.

The Solution: Single source of truth - GSAP controls all transforms.

2. Made the Interactions Feel Better
Added subtle visual enhancements that make hovering feel more premium:

Border Highlight: Cards get a colored border on hover (adapts to light/dark mode)
Enhanced Shadows: Shadow expands when you hover, making the card feel lifted
Icon Scale: The icon inside scales up (1.05x) with a colored glow matching the tech (TypeScript gets blue glow, HTML gets orange, etc.)
Card Scale: The hovered card scales to 1.08x, making it "pop" forward
Smooth Transitions: Added CSS transitions for border and shadow (0.3s ease)
3. Tuned the Push Distance
Reduced the push distance from 160px to 80px. The cards now move away from each other more subtly, which feels less chaotic and more refined, especially on smaller screens.

4. Performance Optimizations
Added will-change: transform for GPU acceleration
Used overwrite: 'auto' in GSAP to prevent animation conflicts
Cleaner state management with a cardStates array instead of transform strings

HTML Code:

TypeScript
Java
CSS3
JavaScript
HTML

CSS code :
/* Bounce Cards Styling */
.floating-card {
position: absolute;
padding: 0;
background: var(--bg-secondary);
backdrop-filter: blur(20px);
border: 3px solid var(--glass-border);
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: var(--shadow-lg);
cursor: pointer;
width: 120px;
aspect-ratio: 1;
overflow: hidden;
transition: border-color 0.3s ease, box-shadow 0.3s ease;
will-change: transform;
}

.floating-card:hover {
border-color: var(--accent-primary);
box-shadow: 0 12px 40px rgba(255, 255, 255, 0.15);
}

body.light-mode .floating-card {
background: rgba(255, 255, 255, 1);
border-color: rgba(220, 220, 220, 1);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}

body.light-mode .floating-card:hover {
border-color: rgba(26, 26, 36, 0.8);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
}

/* Add padding back for non-TypeScript and non-HTML cards */
.floating-card:not([data-language="typescript"]):not([data-language="html"]) {
padding: 20px;
}

.floating-card .lang-icon {
width: 60px;
height: 60px;
object-fit: contain;
filter: drop-shadow(0 2px 8px rgba(255, 255, 255, 0.2));
transition: transform 0.3s ease, filter 0.3s ease;
}

.floating-card:hover .lang-icon {
transform: scale(1.05);
filter: drop-shadow(0 4px 12px rgba(255, 255, 255, 0.3));
}

/* TypeScript card - slightly smaller icon */
.floating-card[data-language="typescript"] .lang-icon {
width: 90%;
height: 90%;
object-fit: contain;
filter: none;
transition: transform 0.3s ease;
}

.floating-card[data-language="typescript"]:hover .lang-icon {
transform: scale(1.05);
filter: drop-shadow(0 4px 12px rgba(41, 98, 218, 0.3));
}

/* HTML card - slightly smaller icon */
.floating-card[data-language="html"] .lang-icon {
width: 90%;
height: 90%;
object-fit: contain;
filter: none;
transition: transform 0.3s ease;
}

.floating-card[data-language="html"]:hover .lang-icon {
transform: scale(1.05);
filter: drop-shadow(0 4px 12px rgba(227, 79, 38, 0.3));
}

/* Cards positioned entirely by GSAP - no CSS positioning needed */

JS Code:
// Bouncing Cards Animation - GSAP handles ALL positioning
if (typeof gsap !== 'undefined') {
const languageCards = document.querySelectorAll('.floating-card[data-language]');

// Card configuration with EXACT positioning
const cardStates = [
{ rotation: 10, x: -170, y: 0 }, // TypeScript
{ rotation: 5, x: -85, y: 0 }, // Java
{ rotation: -3, x: 0, y: 0 }, // CSS (center)
{ rotation: -10, x: 85, y: 0 }, // JavaScript
{ rotation: 2, x: 170, y: 0 } // HTML
];

// Initialize cards with proper transform origin
languageCards.forEach((card, index) => {
// Clear any existing transforms first
card.style.transform = '';

const state = cardStates[index];

// Set initial state with GSAP
gsap.set(card, {
  position: 'absolute',
  top: '60%',
  left: '50%',
  xPercent: -50,  // This replaces translate(-50%, -50%)
  yPercent: -50,
  x: state.x,     // Additional offset from center
  y: state.y,     // Should be 0 for all cards
  rotation: state.rotation,
  scale: 0,
  zIndex: 10,
  transformOrigin: 'center center'
});

// Animate in with elastic bounce
gsap.to(card, {
  scale: 1,
  ease: 'elastic.out(1, 0.8)',
  delay: 0.5 + (index * 0.08)
});

});

// Hover push animation
const pushSiblings = (hoveredIdx) => {
languageCards.forEach((card, i) => {
gsap.killTweensOf(card);
const baseState = cardStates[i];

  if (i === hoveredIdx) {
    // Hovered card: remove rotation, bring to front, subtle scale up
    card.style.zIndex = '100';
    gsap.to(card, {
      x: baseState.x,
      y: baseState.y,  // Maintain baseline
      rotation: 0,
      scale: 1.08,
      duration: 0.4,
      ease: 'back.out(1.4)',
      overwrite: 'auto'
    });
  } else {
    // Other cards: push away
    card.style.zIndex = '1';
    const pushDirection = i < hoveredIdx ? -1 : 1;
    const pushDistance = 80;  // Reduced from 160 to 80
    
    gsap.to(card, {
      x: baseState.x + (pushDirection * pushDistance),
      y: baseState.y,  // Keep original Y (which is 0)
      rotation: baseState.rotation,
      duration: 0.4,
      ease: 'back.out(1.4)',
      delay: Math.abs(hoveredIdx - i) * 0.05,
      overwrite: 'auto'
    });
  }
});

};

// Reset animation
const resetSiblings = () => {
languageCards.forEach((card, i) => {
gsap.killTweensOf(card);
const baseState = cardStates[i];
card.style.zIndex = '10';

  gsap.to(card, {
    x: baseState.x,
    y: baseState.y,
    rotation: baseState.rotation,
    scale: 1,
    duration: 0.4,
    ease: 'back.out(1.4)',
    overwrite: 'auto'
  });
});

};

// Event listeners
languageCards.forEach((card, index) => {
card.addEventListener('mouseenter', () => pushSiblings(index));
});

const heroVisual = document.querySelector('.hero-visual');
if (heroVisual) {
heroVisual.addEventListener('mouseleave', resetSiblings);
}
}

Validations

  • I have checked other issues to see if my issue was already discussed or addressed

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions