Add to cart animation
Lumière Éthéré: Ethereal E-commerce UI with Glassmorphism & GSAP Animations
CSS
GSAP
HTML
JavaScript
An exquisitely designed e-commerce interface, "Lumière Éthéré," showcasing a refined light theme with warm, ethereal aesthetics. The highlight is an elegant glassmorphism panel that appears on product hover, featuring a softly glowing "Add to Bag" button. Smooth, sophisticated animations are powered by GSAP, while AOS (Animate On Scroll) provides graceful page load effects. The design prioritizes a luxurious user experience with delicate typography (Playfair Display and Lato), ample whitespace, subtle shadows, and a cohesive warm gold accent palette. A slide-in cart panel offers intuitive management of selected items with polished transitions.
Lumière Éthéré: Crafting a Celestial Shopping Experience with Advanced UI/UX
"Lumière Éthéré" represents a significant step towards a more luxurious and refined e-commerce user interface. This project reimagines the online shopping experience by infusing it with an ethereal, light-filled aesthetic, moving away from conventional designs to create something truly distinctive. The core color palette revolves around warm off-whites, creams, and a muted, elegant gold accent, evoking a sense of timeless sophistication and gentle luminosity. Typography plays a crucial role, with the classic elegance of "Playfair Display" for headings contrasted against the clean readability of "Lato" for body text, striking a balance between artistry and usability.
The standout feature is the "Glassmorphism Hover Add-to-Cart Panel." Upon hovering over a product card, a semi-transparent panel, crafted with a subtle warm glass effect (backdrop-filter), gracefully slides up from the bottom of the card. This panel houses an "Add to Bag" button that not only fits the pill-shaped, modern aesthetic but also features a soft, radial gold glow, enhancing its visual appeal and interactivity. This interaction is powered by GSAP, ensuring the animation is fluid and responsive. The product cards themselves are designed with ample whitespace, delicate borders, and a gentle lift-and-shadow effect on hover, contributing to the overall premium feel. Initial page loading is enhanced with AOS (Animate On Scroll) library, providing "zoom-in-up" effects for product cards with staggered delays, creating a dynamic and engaging entry sequence.
Functionality remains paramount. Clicking the main navigation's cart icon smoothly slides in a cart panel from the right. This panel, consistent with the ethereal theme, allows users to view their selected items, adjust quantities with elegantly styled buttons, and remove products with subtle animations that involve collapsing height for a clean visual departure. The cart panel also features a custom scrollbar and a refined empty state message. The entire experience, from browsing products to managing the cart, is orchestrated with GSAP to ensure all transitions are seamless, contributing to an immersive and delightful user journey. The code is self-contained in a single HTML file, utilizing CDNs for GSAP, AOS, and Font Awesome.
The standout feature is the "Glassmorphism Hover Add-to-Cart Panel." Upon hovering over a product card, a semi-transparent panel, crafted with a subtle warm glass effect (backdrop-filter), gracefully slides up from the bottom of the card. This panel houses an "Add to Bag" button that not only fits the pill-shaped, modern aesthetic but also features a soft, radial gold glow, enhancing its visual appeal and interactivity. This interaction is powered by GSAP, ensuring the animation is fluid and responsive. The product cards themselves are designed with ample whitespace, delicate borders, and a gentle lift-and-shadow effect on hover, contributing to the overall premium feel. Initial page loading is enhanced with AOS (Animate On Scroll) library, providing "zoom-in-up" effects for product cards with staggered delays, creating a dynamic and engaging entry sequence.
Functionality remains paramount. Clicking the main navigation's cart icon smoothly slides in a cart panel from the right. This panel, consistent with the ethereal theme, allows users to view their selected items, adjust quantities with elegantly styled buttons, and remove products with subtle animations that involve collapsing height for a clean visual departure. The cart panel also features a custom scrollbar and a refined empty state message. The entire experience, from browsing products to managing the cart, is orchestrated with GSAP to ensure all transitions are seamless, contributing to an immersive and delightful user journey. The code is self-contained in a single HTML file, utilizing CDNs for GSAP, AOS, and Font Awesome.
HTML (html)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Lumière Éthéré - Celestial Shopping</title>
<!-- Google Fonts: Playfair Display (Serif for headings) & Lato (Sans-serif for body) -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;600&family=Lato:wght@300;400;700&display=swap"
rel="stylesheet"
/>
<!-- Font Awesome for icons -->
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"
/>
<!-- GSAP for animations -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
<!-- AOS (Animate On Scroll) for page load animations -->
<link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet" />
<script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<header data-aos="fade-down" data-aos-duration="1000">
<div class="container">
<nav>
<div class="logo">
<i class="far fa-lightbulb"></i>
<!-- Changed icon for Lumière -->
<span>Lumière</span>
</div>
<div class="nav-cart-icon-wrapper" id="navCartIconWrapper">
<i class="fas fa-shopping-bag nav-cart-icon"></i>
<span class="nav-cart-count" id="navCartCount">0</span>
</div>
</nav>
</div>
</header>
<main class="container">
<div class="products-grid" id="productsGrid">
<!-- Products will be injected by JavaScript -->
</div>
</main>
<div class="cart-overlay" id="cartOverlay"></div>
<div class="cart-panel" id="cartPanel">
<div class="cart-panel__header">
<div class="cart-panel__title">
<i class="fas fa-shopping-basket"></i>
<span>Your Selection</span>
</div>
<button class="cart-panel__close-btn" id="closeCartBtn">
<i class="fas fa-times"></i>
</button>
</div>
<div class="cart-panel__items" id="cartItemsContainer">
<div class="cart-panel__empty-cart" id="emptyCartMessage">
<i class="far fa-folder-open"></i>
<p>Your bag awaits treasures.</p>
<p>Discover something luminous!</p>
</div>
</div>
<div class="cart-panel__footer">
<div class="cart-panel__total">
<span>Subtotal:</span>
<span class="cart-panel__total-amount" id="cartTotalAmount"
>$0.00</span
>
</div>
<button class="checkout-btn" id="checkoutBtn" disabled>
<i class="fas fa-shield-alt"></i>
<span>Secure Checkout</span>
</button>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
CSS (css)
:root {
--bg-main: #f9f6f2; /* Warm, off-white, like parchment */
--bg-card: #ffffff;
--bg-glass-panel: rgba(245, 240, 230, 0.4); /* Warmer, very subtle glass */
--bg-cart-panel: #faf7f4; /* Slightly off-white for cart */
--text-primary: #3d322c; /* Deep, warm brown */
--text-secondary: #7a6a5f; /* Softer brown */
--text-muted: #b0a399;
--accent-gold: #c0a080; /* Muted, elegant gold */
--accent-gold-light: #d4b89c;
--accent-gold-glow: 0 0 12px var(--accent-gold-light),
0 0 24px rgba(212, 184, 156, 0.6);
--accent-gold-glow-soft: 0 0 15px rgba(212, 184, 156, 0.3);
--border-subtle: #eae3db; /* Very light, warm border */
--shadow-delicate: 0 5px 20px rgba(61, 50, 44, 0.05);
--shadow-ethereal: 0 10px 30px rgba(61, 50, 44, 0.07);
--shadow-interactive-glow: 0 6px 20px rgba(192, 160, 128, 0.2);
--radius-elegant: 10px;
--radius-pill: 60px;
--font-heading: "Playfair Display", serif;
--font-body: "Lato", sans-serif;
--transition-smooth: 0.5s cubic-bezier(0.4, 0, 0.2, 1);
--transition-gentle: 0.7s cubic-bezier(0.4, 0, 0.2, 1);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font-body);
background-color: var(--bg-main);
color: var(--text-primary);
line-height: 1.7;
min-height: 100vh;
overflow-x: hidden;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.container {
max-width: 1320px; /* Wider for more space */
margin: 0 auto;
padding: 0 2rem;
}
/* Header */
header {
background-color: rgba(249, 246, 242, 0.8); /* Semi-transparent warm white */
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
box-shadow: 0 2px 8px rgba(61, 50, 44, 0.04);
position: sticky;
top: 0;
z-index: 1000;
padding: 1.25rem 0;
}
nav {
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
font-family: var(--font-heading);
font-size: 2.2rem; /* Larger, more prominent */
font-weight: 500; /* Playfair looks good a bit lighter */
display: flex;
align-items: center;
gap: 0.6rem;
color: var(--text-primary);
letter-spacing: 0.5px;
}
.logo i {
color: var(--accent-gold);
font-size: 1.8rem; /* Slightly smaller icon */
filter: drop-shadow(0 0 5px var(--accent-gold-glow-soft));
}
.nav-cart-icon-wrapper {
position: relative;
cursor: pointer;
}
.nav-cart-icon {
font-size: 1.7rem;
color: var(--text-secondary);
transition: color 0.3s ease, transform 0.3s ease, filter 0.3s ease;
}
.nav-cart-icon-wrapper:hover .nav-cart-icon {
color: var(--accent-gold);
transform: scale(1.12);
filter: drop-shadow(0 0 6px var(--accent-gold-glow-soft));
}
.nav-cart-count {
position: absolute;
top: -10px;
right: -12px;
background: var(--accent-gold);
color: var(--bg-card);
font-size: 0.75rem;
font-weight: 700;
width: 22px;
height: 22px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 3px 8px rgba(192, 160, 128, 0.5);
border: 2px solid var(--bg-main); /* Match body for seamlessness */
}
/* Product Grid */
.products-grid {
display: grid;
grid-template-columns: repeat(
auto-fill,
minmax(300px, 1fr)
); /* Slightly larger minmax */
gap: 3rem; /* Generous gap */
padding: 4rem 0;
}
.product-card {
background-color: var(--bg-card);
border-radius: var(--radius-elegant);
overflow: hidden;
box-shadow: var(--shadow-delicate);
transition: transform var(--transition-smooth),
box-shadow var(--transition-smooth);
display: flex;
flex-direction: column;
position: relative;
border: 1px solid var(--border-subtle); /* Delicate border */
}
.product-card:hover {
transform: translateY(-10px) scale(1.01); /* More subtle scale */
box-shadow: var(--shadow-ethereal);
}
.product-card__image-container {
position: relative;
height: 280px; /* Taller images */
overflow: hidden;
background-color: #f5f5f5;
}
.product-card__image-container img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.7s cubic-bezier(0.165, 0.84, 0.44, 1); /* Slower, smoother zoom */
}
.product-card:hover .product-card__image-container img {
transform: scale(1.08);
}
.product-card__info {
padding: 1.75rem; /* More padding */
display: flex;
flex-direction: column;
flex-grow: 1;
text-align: left; /* Align left for more traditional luxury */
}
.product-card__title {
font-family: var(--font-heading);
font-size: 1.4rem; /* Larger title */
font-weight: 500;
margin-bottom: 0.6rem;
color: var(--text-primary);
line-height: 1.3;
}
.product-card__price {
font-size: 1.2rem;
font-weight: 700;
font-family: var(--font-body); /* Body font for price */
color: var(--accent-gold);
margin-bottom: 1.25rem;
}
/* Glassmorphism Hover Panel */
.product-card__hover-panel {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 100px; /* Slightly taller */
background: var(--bg-glass-panel);
backdrop-filter: blur(10px); /* Softer blur */
-webkit-backdrop-filter: blur(10px);
border-top: 1px solid rgba(192, 160, 128, 0.15); /* Subtle gold-tinted border */
border-radius: 0 0 var(--radius-elegant) var(--radius-elegant);
display: flex;
justify-content: center;
align-items: center;
padding: 1rem;
opacity: 0;
transform: translateY(50px); /* Start further down */
visibility: hidden;
pointer-events: none;
}
.product-card:hover .product-card__hover-panel {
pointer-events: auto;
}
.hover-add-to-cart-btn {
background: var(--accent-gold);
color: var(--bg-main); /* Contrast with gold */
border: none;
padding: 0.9rem 2.5rem; /* Generous padding */
border-radius: var(--radius-pill);
font-weight: 600;
font-size: 1rem;
letter-spacing: 0.5px;
cursor: pointer;
transition: transform 0.3s ease, box-shadow 0.3s ease,
background-color 0.3s ease;
position: relative;
box-shadow: var(--shadow-interactive-glow);
opacity: 0; /* Start hidden, fade in with panel */
transform: scale(0.9);
}
.hover-add-to-cart-btn::before {
content: "";
position: absolute;
top: -4px;
left: -4px;
right: -4px;
bottom: -4px;
border-radius: var(--radius-pill);
background: radial-gradient(
circle,
rgba(212, 184, 156, 0.5) 0%,
rgba(212, 184, 156, 0) 70%
);
filter: blur(10px);
z-index: -1;
opacity: 0;
transition: opacity 0.4s ease;
}
.hover-add-to-cart-btn:hover {
transform: translateY(-4px) scale(1.02); /* More subtle hover scale */
background-color: var(--accent-gold-light);
box-shadow: 0 10px 25px rgba(192, 160, 128, 0.3);
}
.product-card:hover .hover-add-to-cart-btn::before {
opacity: 0.9;
}
.hover-add-to-cart-btn.added {
background-color: var(--text-muted) !important;
color: var(--bg-main);
pointer-events: none;
}
.hover-add-to-cart-btn.added::after {
content: "Added to Bag";
}
.hover-add-to-cart-btn .btn-text,
.hover-add-to-cart-btn .btn-icon {
transition: opacity 0.2s ease;
}
.hover-add-to-cart-btn.added .btn-text,
.hover-add-to-cart-btn.added .btn-icon {
opacity: 0;
display: none;
}
/* Cart Panel */
.cart-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(
61,
50,
44,
0.55
); /* Warmer, slightly darker overlay */
z-index: 1900;
opacity: 0;
visibility: hidden;
}
.cart-panel {
position: fixed;
top: 0;
right: -100%;
width: 100%;
max-width: 420px;
height: 100%;
background-color: var(--bg-cart-panel);
z-index: 2000;
box-shadow: -10px 0 35px rgba(61, 50, 44, 0.1);
display: flex;
flex-direction: column;
border-left: 1px solid var(--border-subtle);
}
.cart-panel__header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.75rem; /* More padding */
border-bottom: 1px solid var(--border-subtle);
}
.cart-panel__title {
font-family: var(--font-heading);
font-size: 1.6rem;
font-weight: 500;
display: flex;
align-items: center;
gap: 0.8rem;
color: var(--text-primary);
}
.cart-panel__title i {
color: var(--accent-gold);
}
.cart-panel__close-btn {
background: none;
border: none;
font-size: 1.8rem;
cursor: pointer;
color: var(--text-secondary);
transition: transform 0.4s ease, color 0.4s ease;
}
.cart-panel__close-btn:hover {
transform: rotate(180deg) scale(1.1);
color: var(--accent-gold);
}
.cart-panel__items {
flex-grow: 1;
overflow-y: auto;
padding: 1.75rem;
}
.cart-panel__items::-webkit-scrollbar {
width: 7px;
}
.cart-panel__items::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.03);
border-radius: 10px;
}
.cart-panel__items::-webkit-scrollbar-thumb {
background: var(--accent-gold-light);
border-radius: 10px;
}
.cart-panel__items::-webkit-scrollbar-thumb:hover {
background: var(--accent-gold);
}
.cart-panel__empty-cart {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: var(--text-secondary);
text-align: center;
padding: 2rem;
}
.cart-panel__empty-cart i {
font-size: 5.5rem;
margin-bottom: 1.5rem;
color: var(--text-muted);
}
.cart-panel__empty-cart p {
font-size: 1.1rem;
font-family: var(--font-heading);
font-weight: 400;
}
.cart-item {
display: flex;
gap: 1.25rem;
padding: 1.25rem;
border-radius: var(--radius-elegant);
background-color: var(--bg-card);
margin-bottom: 1.25rem;
box-shadow: var(--shadow-delicate);
position: relative;
border: 1px solid var(--border-subtle);
}
.cart-item__img-container {
width: 85px;
height: 85px;
border-radius: var(--radius-elegant);
overflow: hidden;
flex-shrink: 0;
background-color: #f0f0f0;
border: 1px solid var(--border-subtle);
}
.cart-item__img-container img {
width: 100%;
height: 100%;
object-fit: cover;
}
.cart-item__info {
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.cart-item__title {
font-family: var(--font-heading);
font-weight: 500;
font-size: 1.1rem;
margin-bottom: 0.3rem;
color: var(--text-primary);
}
.cart-item__price {
color: var(--accent-gold);
font-weight: 700;
font-size: 0.95rem;
margin-bottom: 0.6rem;
}
.cart-item__quantity-controls {
display: flex;
align-items: center;
gap: 0.8rem;
}
.quantity-btn {
width: 30px;
height: 30px;
border-radius: 50%;
border: 1px solid var(--border-subtle);
background-color: var(--bg-main);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
color: var(--text-secondary);
transition: all 0.3s ease;
font-size: 0.9rem;
}
.quantity-btn:hover {
background-color: var(--accent-gold);
color: var(--bg-card);
border-color: var(--accent-gold);
transform: scale(1.05);
}
.cart-item__quantity {
font-weight: 600;
font-size: 1rem;
min-width: 20px;
text-align: center;
color: var(--text-primary);
}
.cart-item__remove-btn {
position: absolute;
top: 1rem;
right: 1rem;
color: var(--text-muted);
background: none;
border: none;
cursor: pointer;
font-size: 1.3rem;
padding: 0.2rem;
transition: color 0.3s ease, transform 0.3s ease;
}
.cart-item__remove-btn:hover {
color: #a8523c; /* Rusty red */
transform: scale(1.15) rotate(5deg);
}
.cart-panel__footer {
padding: 1.75rem;
border-top: 1px solid var(--border-subtle);
background-color: var(--bg-main); /* Consistent with body */
}
.cart-panel__total {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
color: var(--text-secondary);
}
.cart-panel__total span:first-child {
font-size: 1.1rem;
font-weight: 500;
}
.cart-panel__total-amount {
font-size: 1.5rem;
font-weight: 700;
color: var(--accent-gold);
}
.checkout-btn {
width: 100%;
padding: 1rem;
border-radius: var(--radius-pill);
background: var(--accent-gold);
color: var(--bg-main);
border: none;
font-weight: 600;
font-size: 1.05rem;
letter-spacing: 0.5px;
font-family: var(--font-body);
cursor: pointer;
transition: transform 0.3s ease, box-shadow 0.3s ease,
background-color 0.3s ease;
display: flex;
justify-content: center;
align-items: center;
gap: 0.75rem;
box-shadow: var(--shadow-interactive-glow);
}
.checkout-btn:hover:not(:disabled) {
transform: translateY(-4px) scale(1.01);
background-color: var(--accent-gold-light);
box-shadow: 0 10px 25px rgba(192, 160, 128, 0.35);
}
.checkout-btn:disabled {
background: var(--text-muted);
color: var(--bg-main);
cursor: not-allowed;
box-shadow: none;
transform: none;
}
/* Flying Image Animation */
.flying-product-thumbnail {
position: fixed;
z-index: 2500;
pointer-events: none;
border-radius: var(--radius-elegant);
box-shadow: var(--shadow-ethereal);
overflow: hidden;
border: 2px solid var(--accent-gold-light);
}
.flying-product-thumbnail img {
width: 100%;
height: 100%;
object-fit: cover;
}
/* Responsive */
@media (max-width: 992px) {
.products-grid {
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 2rem;
}
}
@media (max-width: 768px) {
.container {
padding: 0 1.5rem;
}
.cart-panel {
max-width: 90%;
}
.product-card__title {
font-size: 1.25rem;
}
.product-card__price {
font-size: 1.1rem;
}
.hover-add-to-cart-btn {
padding: 0.8rem 2rem;
font-size: 0.9rem;
}
}
@media (max-width: 480px) {
.container {
padding: 0 1rem;
}
.logo {
font-size: 1.8rem;
}
.logo i {
font-size: 1.5rem;
}
.nav-cart-icon {
font-size: 1.5rem;
}
.products-grid {
grid-template-columns: 1fr;
gap: 2rem;
}
.product-card__image-container {
height: 250px;
} /* Maintain image height */
.product-card__info {
padding: 1.5rem;
}
.cart-panel {
max-width: 100%;
border-radius: 0;
border-left: none;
}
}
JAVASCRIPT (javascript)
document.addEventListener("DOMContentLoaded", () => {
AOS.init({
duration: 1000,
delay: 100,
once: true,
offset: 80,
easing: "ease-out-cubic",
});
const productData = [
// Using placeholder.com to ensure images are always present
{
id: "p1",
name: "Ethereal Cashmere Wrap",
price: 185.0,
image:
"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcStHi6IhFQgJ_k_hF0QDBIgFzzqu6L0CGv_Sg&s",
},
{
id: "p2",
name: "Celestial Pearl Necklace",
price: 220.5,
image:
"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQOp28QF6UmIqNesM1VZBONbD2h26MtBtc5gg&s",
},
{
id: "p3",
name: "Gilded Leaf Brooch",
price: 95.0,
image:
"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTkHIcofGJrHozjPPW2e6QUzv_Xdb9l4_9H2g&s",
},
{
id: "p4",
name: "Velvet Moon Pouch",
price: 78.0,
image:
"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQJlW4f7xdczsgfG9B6sjpA244DeA5wOno7wQ&s",
},
{
id: "p5",
name: "Antique Lace Fan",
price: 110.25,
image:
"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQpZaXXiXwOdOT5VWTFIiRzR5Q54ZkyvL3mTw&s",
},
{
id: "p6",
name: "Sunstone Charm Bracelet",
price: 132.0,
image:
"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQfabRKWww4idH2JRdF2_GvlLhrg8HCsyod9w&s",
},
];
let cart = [];
const productsGrid = document.getElementById("productsGrid");
const navCartIconWrapper = document.getElementById("navCartIconWrapper");
const navCartCount = document.getElementById("navCartCount");
const cartOverlay = document.getElementById("cartOverlay");
const cartPanel = document.getElementById("cartPanel");
const closeCartBtn = document.getElementById("closeCartBtn");
const cartItemsContainer = document.getElementById("cartItemsContainer");
const emptyCartMessage = document.getElementById("emptyCartMessage");
const cartTotalAmount = document.getElementById("cartTotalAmount");
const checkoutBtn = document.getElementById("checkoutBtn");
function renderProducts() {
productsGrid.innerHTML = "";
productData.forEach((product, index) => {
const productCard = document.createElement("div");
productCard.className = "product-card";
productCard.setAttribute("data-aos", "zoom-in-up");
productCard.setAttribute("data-aos-delay", (index % 3) * 100); // Stagger delay
productCard.innerHTML = `
<div class="product-card__image-container">
<img src="${product.image}" alt="${
product.name
}" id="product-img-${
product.id
}" onerror="this.src='https://via.placeholder.com/300x400/EAE3DB/B0A399?text=Lumi%C3%A8re'">
</div>
<div class="product-card__info">
<h3 class="product-card__title">${product.name}</h3>
<p class="product-card__price">$${product.price.toFixed(
2
)}</p>
</div>
<div class="product-card__hover-panel">
<button class="hover-add-to-cart-btn" data-id="${
product.id
}">
<span class="btn-text">Add to Bag</span>
<span class="btn-icon"><i class="fas fa-plus"></i></span>
</button>
</div>
`;
productsGrid.appendChild(productCard);
const hoverPanel = productCard.querySelector(
".product-card__hover-panel"
);
const addToCartBtnInPanel = hoverPanel.querySelector(
".hover-add-to-cart-btn"
);
productCard.addEventListener("mouseenter", () => {
gsap.to(hoverPanel, {
y: "0%",
opacity: 1,
visibility: "visible",
duration: 0.5,
ease: "sine.out",
});
gsap.to(addToCartBtnInPanel, {
opacity: 1,
scale: 1,
duration: 0.4,
delay: 0.15, // Button appears slightly after panel
ease: "back.out(1.4)",
});
});
productCard.addEventListener("mouseleave", () => {
gsap.to(hoverPanel, {
y: "50px",
opacity: 0,
visibility: "hidden",
duration: 0.4,
ease: "sine.in",
});
gsap.to(addToCartBtnInPanel, {
// Reset button for next hover
opacity: 0,
scale: 0.9,
duration: 0.2,
ease: "power1.in",
});
});
});
}
function flyProductToCart(productId, buttonElement) {
const productImgElement = document.getElementById(
`product-img-${productId}`
);
if (!productImgElement) return;
const productImgRect = productImgElement.getBoundingClientRect();
const cartIconRect = navCartIconWrapper.getBoundingClientRect();
const flyingThumb = document.createElement("div");
flyingThumb.className = "flying-product-thumbnail";
flyingThumb.innerHTML = `<img src="${productImgElement.src}" alt="flying item" onerror="this.src='https://via.placeholder.com/80x80/EAE3DB/B0A399?text=...'">`;
document.body.appendChild(flyingThumb);
gsap.set(flyingThumb, {
left: productImgRect.left + productImgRect.width / 2 - 25,
top: productImgRect.top + productImgRect.height / 2 - 25,
width: 50,
height: 50,
opacity: 1,
});
const tl = gsap.timeline({
onComplete: () => {
flyingThumb.remove();
gsap.fromTo(
navCartIconWrapper.querySelector(".nav-cart-icon"),
{ scale: 1, y: 0 },
{
scale: 1.3,
y: -3,
duration: 0.2,
ease: "power1.out",
yoyo: true,
repeat: 1,
}
);
gsap.to(navCartIconWrapper.querySelector(".nav-cart-icon"), {
scale: 1,
y: 0,
duration: 0.3,
delay: 0.4,
ease: "elastic.out(1,0.5)",
});
},
});
tl.to(flyingThumb, {
x:
cartIconRect.left +
cartIconRect.width / 2 -
(productImgRect.left + productImgRect.width / 2),
y:
cartIconRect.top +
cartIconRect.height / 2 -
(productImgRect.top + productImgRect.height / 2),
width: 25,
height: 25,
opacity: 0.5,
rotation: gsap.utils.random(90, 180) * (Math.random() < 0.5 ? -1 : 1),
duration: 0.9,
ease: "power2.inOut", // Using a slightly different ease
}).to(
flyingThumb,
{ scale: 0.3, opacity: 0, duration: 0.2, ease: "power1.in" },
"-=0.1"
);
}
function handleAddToCart(event) {
const button = event.target.closest(".hover-add-to-cart-btn");
if (!button || button.classList.contains("added")) return;
const productId = button.dataset.id;
const product = productData.find((p) => p.id === productId);
if (!product) return;
flyProductToCart(productId, button);
button.classList.add("added");
gsap.to(button, { duration: 0.2, "--button-text-opacity": 0 }); // For custom property if used
setTimeout(() => {
button.classList.remove("added");
gsap.to(button, { duration: 0.2, "--button-text-opacity": 1 });
}, 1800);
const existingItem = cart.find((item) => item.id === productId);
if (existingItem) {
existingItem.quantity++;
} else {
cart.push({ ...product, quantity: 1 });
}
updateCart();
}
function updateCart() {
renderCartItems();
updateCartCount();
updateCartTotal();
updateCheckoutButtonState();
}
function renderCartItems() {
if (cart.length === 0) {
gsap.fromTo(
emptyCartMessage,
{ opacity: 0, y: 20 },
{
display: "flex",
opacity: 1,
y: 0,
duration: 0.4,
ease: "power2.out",
}
);
cartItemsContainer.innerHTML = "";
if (!cartItemsContainer.contains(emptyCartMessage)) {
cartItemsContainer.appendChild(emptyCartMessage);
}
} else {
gsap.to(emptyCartMessage, {
opacity: 0,
y: 20,
duration: 0.3,
ease: "power2.in",
onComplete: () => (emptyCartMessage.style.display = "none"),
});
cartItemsContainer.innerHTML = "";
cart.forEach((item) => {
const cartItemDiv = document.createElement("div");
cartItemDiv.className = "cart-item";
gsap.set(cartItemDiv, { opacity: 0, x: -20 }); // Initial state for GSAP
cartItemDiv.innerHTML = `
<div class="cart-item__img-container">
<img src="${item.image}" alt="${
item.name
}" onerror="this.src='https://via.placeholder.com/85x85/EAE3DB/B0A399?text=N/A'">
</div>
<div class="cart-item__info">
<div>
<h4 class="cart-item__title">${item.name}</h4>
<p class="cart-item__price">$${(
item.price * item.quantity
).toFixed(2)}</p>
</div>
<div class="cart-item__quantity-controls">
<button class="quantity-btn decrease-qty" data-id="${
item.id
}"><i class="fas fa-minus"></i></button>
<span class="cart-item__quantity">${
item.quantity
}</span>
<button class="quantity-btn increase-qty" data-id="${
item.id
}"><i class="fas fa-plus"></i></button>
</div>
</div>
<button class="cart-item__remove-btn" data-id="${
item.id
}"><i class="fas fa-times"></i></button>
`;
cartItemsContainer.appendChild(cartItemDiv);
gsap.to(cartItemDiv, {
opacity: 1,
x: 0,
duration: 0.5,
delay: cart.indexOf(item) * 0.07,
ease: "power3.out",
});
});
}
}
function updateCartCount() {
const totalItems = cart.reduce((sum, item) => sum + item.quantity, 0);
navCartCount.textContent = totalItems;
if (totalItems > 0 || navCartCount.textContent !== "0") {
gsap.fromTo(
navCartCount,
{ scale: 1.6, opacity: 0, y: -5 },
{
scale: 1,
opacity: 1,
y: 0,
duration: 0.4,
ease: "elastic.out(1.1, 0.6)",
}
);
}
}
function updateCartTotal() {
const total = cart.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
cartTotalAmount.textContent = `$${total.toFixed(2)}`;
}
function updateCheckoutButtonState() {
checkoutBtn.disabled = cart.length === 0;
}
function handleCartActions(event) {
const target = event.target.closest("button");
if (!target) return;
const productId = target.dataset.id;
let itemChanged = false;
const itemIndex = cart.findIndex((i) => i.id === productId);
if (target.classList.contains("increase-qty") && itemIndex !== -1) {
cart[itemIndex].quantity++;
itemChanged = true;
} else if (target.classList.contains("decrease-qty") && itemIndex !== -1) {
cart[itemIndex].quantity--;
itemChanged = true;
if (cart[itemIndex].quantity <= 0) {
const itemDiv = target.closest(".cart-item");
gsap.to(itemDiv, {
opacity: 0,
height: 0,
paddingTop: 0,
paddingBottom: 0,
marginTop: 0,
marginBottom: 0,
duration: 0.4,
ease: "power2.in",
onComplete: () => {
cart.splice(itemIndex, 1);
updateCart();
},
});
return;
}
} else if (target.classList.contains("cart-item__remove-btn")) {
const itemToRemoveIndex = cart.findIndex((i) => i.id === productId); // Re-find index for removal
if (itemToRemoveIndex !== -1) {
const itemDiv = target.closest(".cart-item");
gsap.to(itemDiv, {
opacity: 0,
scale: 0.8,
x: 50,
duration: 0.4,
ease: "power2.in",
onComplete: () => {
cart.splice(itemToRemoveIndex, 1);
updateCart();
},
});
return;
}
}
if (itemChanged) updateCart();
}
function openCartPanel() {
cartOverlay.style.visibility = "visible";
gsap.to(cartOverlay, { opacity: 1, duration: 0.5, ease: "sine.out" });
gsap.to(cartPanel, { right: 0, duration: 0.6, ease: "expo.out" });
document.body.style.overflow = "hidden"; // Prevent body scroll when panel is open
}
function closeCartPanel() {
gsap.to(cartPanel, {
right: "-100%",
duration: 0.5,
ease: "expo.in",
});
gsap.to(cartOverlay, {
opacity: 0,
duration: 0.4,
ease: "sine.in",
onComplete: () => {
cartOverlay.style.visibility = "hidden";
document.body.style.overflow = ""; // Restore body scroll
},
});
}
// Event Listeners
productsGrid.addEventListener("click", handleAddToCart);
navCartIconWrapper.addEventListener("click", openCartPanel);
closeCartBtn.addEventListener("click", closeCartPanel);
cartOverlay.addEventListener("click", closeCartPanel);
cartItemsContainer.addEventListener("click", handleCartActions);
checkoutBtn.addEventListener("click", () => {
if (cart.length > 0) {
alert(
`Checkout for $${cartTotalAmount.textContent}.\n(Lumière Éthéré Demo)`
);
cart = [];
updateCart();
closeCartPanel();
}
});
renderProducts();
updateCart();
});
Download Source Code
Get the complete source code for this tutorial to use in your projects.
Comments (0)
No comments yet. Be the first to comment!