diff --git a/src/components/merch/FilterBar.css b/src/components/merch/FilterBar.css index 08867995..58555b77 100644 --- a/src/components/merch/FilterBar.css +++ b/src/components/merch/FilterBar.css @@ -1,29 +1,22 @@ -/* Filter Bar Styles */ +/* Filter Sidebar Styles */ .filter-bar { background: var(--ifm-card-background-color); - border-bottom: 1px solid var(--ifm-color-emphasis-200); - position: sticky; - top: 60px; - z-index: 100; + border: 1px solid var(--ifm-color-emphasis-200); + border-radius: 12px; + padding: 1.5rem; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); } .filter-bar-container { - max-width: 1400px; - margin: 0 auto; - padding: 1.5rem 2rem; display: flex; - justify-content: space-between; - align-items: center; + flex-direction: column; gap: 2rem; - flex-wrap: wrap; } /* Filter Section */ .filter-section { - flex: 1; - min-width: 300px; + width: 100%; } .filter-header { @@ -44,35 +37,52 @@ /* Category Filters */ .category-filters { display: flex; + flex-direction: column; gap: 0.75rem; - flex-wrap: wrap; } .category-button { display: flex; align-items: center; - gap: 0.5rem; - padding: 0.5rem 1rem; - background: var(--ifm-color-emphasis-100); - border: 2px solid transparent; + gap: 0.75rem; + padding: 0.75rem 1rem; + background: #f5f5f5; + border: 2px solid #e0e0e0; border-radius: 8px; font-size: 0.875rem; font-weight: 500; color: #2d3748; cursor: pointer; transition: all 0.2s ease; + width: 100%; + text-align: left; } .category-button:hover { - background: var(--ifm-color-emphasis-200); - transform: translateY(-2px); + background: #ffffff; + border-color: #cccccc; + transform: translateX(4px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + color: #1a1a1a; +} + +.category-button:active { + transform: translateX(2px); } .category-button.active { - background: var(--ifm-color-primary); - border-color: var(--ifm-color-primary); + background: linear-gradient(135deg, #667eea 0%, #5b47d6 100%); + border-color: #667eea; color: #ffffff !important; font-weight: 600; + box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); +} + +.category-button.active:hover { + background: linear-gradient(135deg, #818cf8 0%, #06b6d4 100%); + border-color: #818cf8; + transform: translateX(4px); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); } .category-icon { @@ -85,20 +95,20 @@ /* Sort Section */ .sort-section { - min-width: 200px; + width: 100%; } .sort-select { width: 100%; padding: 0.625rem 1rem; - background: var(--ifm-color-emphasis-100); - border: 2px solid var(--ifm-color-emphasis-200); + background: #f5f5f5; + border: 2px solid #e0e0e0; border-radius: 8px; font-size: 0.875rem; font-weight: 500; color: var(--ifm-font-color-base); cursor: pointer; - transition: all 0.2s ease; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 9L1 4h10z'/%3E%3C/svg%3E"); background-repeat: no-repeat; @@ -107,46 +117,47 @@ } .sort-select:hover { - border-color: var(--ifm-color-primary); + background-color: #ffffff; + border-color: #999999; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } .sort-select:focus { outline: none; - border-color: var(--ifm-color-primary); - box-shadow: 0 0 0 3px var(--ifm-color-primary-lightest); + border-color: #667eea; + background-color: #ffffff; + box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.15), 0 2px 8px rgba(102, 126, 234, 0.2); } /* Responsive Design */ -@media (max-width: 768px) { - .filter-bar-container { - padding: 1rem; - flex-direction: column; - align-items: stretch; - } - - .filter-section { - min-width: 100%; +@media (max-width: 992px) { + .filter-bar { + margin-bottom: 2rem; } .category-filters { - gap: 0.5rem; + flex-direction: row; + flex-wrap: wrap; } .category-button { - padding: 0.5rem 0.75rem; - font-size: 0.8125rem; + width: auto; } +} - .category-icon { - font-size: 1rem; +@media (max-width: 768px) { + .filter-bar-container { + padding: 1rem; + gap: 1.5rem; } - .sort-section { - min-width: 100%; + .category-filters { + gap: 0.5rem; } - .filter-bar { - top: 0; + .category-button { + padding: 0.5rem 0.75rem; + font-size: 0.8125rem; } } @@ -157,21 +168,43 @@ } [data-theme="dark"] .category-button { - background: var(--ifm-color-emphasis-200); + background: #2a2a2a; + border-color: #404040; color: var(--ifm-font-color-base); } +[data-theme="dark"] .category-button:hover { + background: #3a3a3a; + border-color: #555555; + box-shadow: 0 8px 16px rgba(255, 255, 255, 0.1); +} + [data-theme="dark"] .category-button.active { - background: rgba(16, 185, 129, 0.15); - border-color: var(--ifm-color-primary); - color: #10b981 !important; + background: rgba(255, 255, 255, 0.15); + border-color: #ffffff; + color: #ffffff !important; + box-shadow: 0 6px 12px rgba(255, 255, 255, 0.15); } -[data-theme="dark"] .category-button:hover { - background: var(--ifm-color-emphasis-300); +[data-theme="dark"] .category-button.active:hover { + background: rgba(255, 255, 255, 0.25); + border-color: #ffffff; + box-shadow: 0 10px 20px rgba(255, 255, 255, 0.2); } [data-theme="dark"] .sort-select { - background: var(--ifm-color-emphasis-200); - border-color: var(--ifm-color-emphasis-300); + background: #2a2a2a; + border-color: #404040; +} + +[data-theme="dark"] .sort-select:hover { + background: #3a3a3a; + border-color: #555555; + box-shadow: 0 6px 12px rgba(255, 255, 255, 0.08); +} + +[data-theme="dark"] .sort-select:focus { + border-color: #ffffff; + background: #3a3a3a; + box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.1), 0 6px 12px rgba(255, 255, 255, 0.08); } diff --git a/src/components/merch/ProductCard.css b/src/components/merch/ProductCard.css index 0a171075..1bbc1eb1 100644 --- a/src/components/merch/ProductCard.css +++ b/src/components/merch/ProductCard.css @@ -4,16 +4,18 @@ background: var(--ifm-card-background-color); border-radius: 16px; overflow: hidden; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); transition: all 0.3s ease; height: 100%; display: flex; flex-direction: column; + border: 1px solid rgba(0, 0, 0, 0.06); } .product-card:hover { - transform: translateY(-8px); - box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15); + transform: translateY(-4px); + box-shadow: 0 12px 24px rgba(102, 126, 234, 0.15); + border-color: rgba(102, 126, 234, 0.3); } /* Image Section */ @@ -58,22 +60,23 @@ } .overlay-button { - background: white; + background: rgba(255, 255, 255, 0.95); border: none; border-radius: 50%; - width: 48px; - height: 48px; + width: 44px; + height: 44px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.2s ease; - color: var(--ifm-color-primary); + color: #667eea; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); } .overlay-button:hover { transform: scale(1.1); - background: var(--ifm-color-primary); + background: linear-gradient(135deg, #667eea 0%, #06b6d4 100%); color: white; } @@ -111,14 +114,16 @@ position: absolute; top: 12px; left: 12px; - background: var(--ifm-color-primary); + background: linear-gradient(135deg, #667eea 0%, #5b47d6 100%); color: white; - padding: 4px 12px; - border-radius: 20px; - font-size: 0.75rem; + padding: 4px 10px; + border-radius: 6px; + font-size: 0.7rem; font-weight: 600; text-transform: uppercase; + letter-spacing: 0.5px; z-index: 3; + box-shadow: 0 2px 8px rgba(102, 126, 234, 0.4); } /* Content Section */ @@ -201,7 +206,7 @@ display: flex; align-items: baseline; font-weight: 700; - color: var(--ifm-color-primary); + color: #5b47d6; } .price-currency { @@ -215,9 +220,10 @@ .product-card-button { display: flex; align-items: center; + justify-content: center; gap: 0.5rem; - padding: 0.625rem 1.25rem; - background: var(--ifm-color-primary); + padding: 0.625rem 1rem; + background: linear-gradient(135deg, #667eea 0%, #5b47d6 100%); color: white; border: none; border-radius: 8px; @@ -226,12 +232,17 @@ cursor: pointer; transition: all 0.2s ease; white-space: nowrap; + box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); } .product-card-button:hover { - background: var(--ifm-color-primary-dark); - transform: translateY(-2px); - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + background: linear-gradient(135deg, #818cf8 0%, #06b6d4 100%); + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); +} + +.product-card-button:active { + transform: translateY(0); } .product-card-button:active { diff --git a/src/components/merch/ProductCard.tsx b/src/components/merch/ProductCard.tsx index 79970044..e53e690d 100644 --- a/src/components/merch/ProductCard.tsx +++ b/src/components/merch/ProductCard.tsx @@ -2,6 +2,7 @@ import React, { useState } from "react"; import { motion } from "framer-motion"; import { ShoppingCart, Heart, Eye } from "lucide-react"; import type { Product } from "../../pages/merch"; +import ProductImageViewer from "./ProductImageViewer"; import "./ProductCard.css"; interface ProductCardProps { @@ -12,6 +13,7 @@ interface ProductCardProps { const ProductCard: React.FC = ({ product, onAddToCart }) => { const [isLiked, setIsLiked] = useState(false); const [isHovered, setIsHovered] = useState(false); + const [viewerOpen, setViewerOpen] = useState(false); const handleAddToCart = (e: React.MouseEvent) => { e.preventDefault(); @@ -23,8 +25,14 @@ const ProductCard: React.FC = ({ product, onAddToCart }) => { setIsLiked(!isLiked); }; + const openViewer = (e: React.MouseEvent) => { + e.preventDefault(); + setViewerOpen(true); + }; + return ( - + = ({ product, onAddToCart }) => { onHoverEnd={() => setIsHovered(false)} >
-
+
{product.title} {isHovered && ( = ({ product, onAddToCart }) => { animate={{ opacity: 1 }} exit={{ opacity: 0 }} > - @@ -103,6 +115,14 @@ const ProductCard: React.FC = ({ product, onAddToCart }) => {
+ + setViewerOpen(false)} + imageUrl={product.image} + title={product.title} + /> + ); }; diff --git a/src/components/merch/ProductGrid.css b/src/components/merch/ProductGrid.css index dcf9ea80..b4e63cb5 100644 --- a/src/components/merch/ProductGrid.css +++ b/src/components/merch/ProductGrid.css @@ -2,9 +2,9 @@ .product-grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); - gap: 2rem; - padding: 2rem 0; + grid-template-columns: repeat(3, 1fr); + gap: 1.75rem; + padding: 0; } /* Empty State */ @@ -43,15 +43,15 @@ /* Responsive Design */ @media (max-width: 1200px) { .product-grid { - grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + grid-template-columns: repeat(2, 1fr); gap: 1.5rem; } } @media (max-width: 768px) { .product-grid { - grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); - gap: 1rem; + grid-template-columns: repeat(2, 1fr); + gap: 1.25rem; } } diff --git a/src/components/merch/ProductImageViewer.css b/src/components/merch/ProductImageViewer.css new file mode 100644 index 00000000..ebaa36e0 --- /dev/null +++ b/src/components/merch/ProductImageViewer.css @@ -0,0 +1,241 @@ +/* Product Image Viewer Styles */ + +.image-viewer-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.95); + backdrop-filter: blur(10px); + z-index: 10000; + display: flex; + align-items: center; + justify-content: center; + padding: 2rem; +} + +.image-viewer-container { + width: 100%; + max-width: 1200px; + height: 90vh; + background: var(--ifm-background-color); + border-radius: 20px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); + display: flex; + flex-direction: column; + overflow: hidden; +} + +/* Header */ +.image-viewer-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1.5rem 2rem; + background: var(--ifm-background-surface-color); + border-bottom: 1px solid var(--ifm-color-emphasis-200); +} + +.image-viewer-title { + font-size: 1.25rem; + font-weight: 700; + margin: 0; + color: var(--ifm-font-color-base); +} + +.image-viewer-close { + background: none; + border: none; + color: var(--ifm-font-color-base); + cursor: pointer; + padding: 0.5rem; + border-radius: 8px; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; +} + +.image-viewer-close:hover { + background: var(--ifm-color-emphasis-200); + transform: rotate(90deg); +} + +/* Content */ +.image-viewer-content { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + background: var(--ifm-color-emphasis-100); + position: relative; + user-select: none; +} + +.image-viewer-image-wrapper { + display: flex; + align-items: center; + justify-content: center; + will-change: transform; +} + +.image-viewer-image { + max-width: 100%; + max-height: 100%; + object-fit: contain; + pointer-events: none; + user-select: none; + -webkit-user-drag: none; +} + +/* Controls */ +.image-viewer-controls { + display: flex; + align-items: center; + justify-content: center; + gap: 0.75rem; + padding: 1.5rem 2rem; + background: var(--ifm-background-surface-color); + border-top: 1px solid var(--ifm-color-emphasis-200); +} + +.control-button { + background: linear-gradient(135deg, #667eea 0%, #5b47d6 100%); + border: none; + color: white; + border-radius: 8px; + padding: 0.75rem 1rem; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + min-width: 44px; + height: 44px; + box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); +} + +.control-button:hover:not(:disabled) { + background: linear-gradient(135deg, #818cf8 0%, #06b6d4 100%); + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); +} + +.control-button:active:not(:disabled) { + transform: translateY(0) scale(0.98); +} + +.control-button:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +.zoom-indicator { + font-size: 0.875rem; + font-weight: 600; + color: var(--ifm-font-color-base); + min-width: 60px; + text-align: center; + padding: 0 0.5rem; +} + +.control-divider { + width: 1px; + height: 32px; + background: var(--ifm-color-emphasis-300); + margin: 0 0.5rem; +} + +/* Instructions */ +.image-viewer-instructions { + display: flex; + align-items: center; + justify-content: center; + gap: 1.5rem; + padding: 1rem 2rem; + background: var(--ifm-background-surface-color); + border-top: 1px solid var(--ifm-color-emphasis-200); + font-size: 0.75rem; + color: var(--ifm-font-color-secondary); +} + +.image-viewer-instructions span { + display: flex; + align-items: center; + gap: 0.375rem; +} + +/* Animations */ +@keyframes pulse { + 0%, 100% { + transform: scale(1); + } + 50% { + transform: scale(1.05); + } +} + +.control-button:hover:not(:disabled) svg { + animation: pulse 0.6s ease-in-out; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .image-viewer-overlay { + padding: 0; + } + + .image-viewer-container { + max-width: 100%; + height: 100vh; + border-radius: 0; + } + + .image-viewer-header, + .image-viewer-controls { + padding: 1rem; + } + + .image-viewer-title { + font-size: 1rem; + } + + .image-viewer-instructions { + padding: 0.75rem 1rem; + gap: 0.75rem; + font-size: 0.7rem; + flex-wrap: wrap; + } + + .control-button { + padding: 0.625rem; + min-width: 40px; + height: 40px; + } + + .zoom-indicator { + font-size: 0.8125rem; + min-width: 50px; + } +} + +/* Dark Mode */ +[data-theme="dark"] .image-viewer-overlay { + background: rgba(0, 0, 0, 0.98); +} + +[data-theme="dark"] .image-viewer-content { + background: #000000; +} + +[data-theme="dark"] .control-button { + background: #ffffff; + color: #1a1a1a; +} + +[data-theme="dark"] .control-button:hover:not(:disabled) { + background: #f0f0f0; + box-shadow: 0 6px 12px rgba(255, 255, 255, 0.2); +} diff --git a/src/components/merch/ProductImageViewer.tsx b/src/components/merch/ProductImageViewer.tsx new file mode 100644 index 00000000..c1113030 --- /dev/null +++ b/src/components/merch/ProductImageViewer.tsx @@ -0,0 +1,231 @@ +import React, { useState, useRef, useEffect } from "react"; +import { motion, AnimatePresence } from "framer-motion"; +import { X, ZoomIn, ZoomOut, RotateCw, Maximize2 } from "lucide-react"; +import "./ProductImageViewer.css"; + +interface ProductImageViewerProps { + isOpen: boolean; + onClose: () => void; + imageUrl: string; + title: string; +} + +const ProductImageViewer: React.FC = ({ + isOpen, + onClose, + imageUrl, + title, +}) => { + const [scale, setScale] = useState(1); + const [position, setPosition] = useState({ x: 0, y: 0 }); + const [rotation, setRotation] = useState(0); + const [isDragging, setIsDragging] = useState(false); + const [dragStart, setDragStart] = useState({ x: 0, y: 0 }); + const imageRef = useRef(null); + + useEffect(() => { + if (isOpen) { + // Reset on open + setScale(1); + setPosition({ x: 0, y: 0 }); + setRotation(0); + } + }, [isOpen]); + + const handleWheel = (e: React.WheelEvent) => { + e.preventDefault(); + const delta = e.deltaY * -0.001; + const newScale = Math.min(Math.max(0.5, scale + delta), 5); + setScale(newScale); + }; + + const handleMouseDown = (e: React.MouseEvent) => { + if (scale > 1) { + setIsDragging(true); + setDragStart({ + x: e.clientX - position.x, + y: e.clientY - position.y, + }); + } + }; + + const handleMouseMove = (e: React.MouseEvent) => { + if (isDragging && scale > 1) { + setPosition({ + x: e.clientX - dragStart.x, + y: e.clientY - dragStart.y, + }); + } + }; + + const handleMouseUp = () => { + setIsDragging(false); + }; + + const handleZoomIn = () => { + setScale(Math.min(scale + 0.5, 5)); + }; + + const handleZoomOut = () => { + setScale(Math.max(scale - 0.5, 0.5)); + }; + + const handleRotate = () => { + setRotation((rotation + 90) % 360); + }; + + const handleReset = () => { + setScale(1); + setPosition({ x: 0, y: 0 }); + setRotation(0); + }; + + const handleKeyDown = (e: KeyboardEvent) => { + if (!isOpen) return; + + switch (e.key) { + case "Escape": + onClose(); + break; + case "+": + case "=": + handleZoomIn(); + break; + case "-": + handleZoomOut(); + break; + case "r": + case "R": + handleRotate(); + break; + case "0": + handleReset(); + break; + } + }; + + useEffect(() => { + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [isOpen, scale, rotation]); + + if (!isOpen) return null; + + return ( + + + e.stopPropagation()} + > + {/* Header */} +
+

{title}

+ +
+ + {/* Image Container */} +
1 ? "grab" : "default" }} + > + + {title} + +
+ + {/* Controls */} +
+ + {Math.round(scale * 100)}% + +
+ + +
+ + {/* Instructions */} +
+ 🖱️ Scroll to zoom + 🤚 Drag to pan + ⌨️ R to rotate + ⌨️ ESC to close +
+ + + + ); +}; + +export default ProductImageViewer; diff --git a/src/components/merch/ShoppingCart.css b/src/components/merch/ShoppingCart.css index 7653d509..73386100 100644 --- a/src/components/merch/ShoppingCart.css +++ b/src/components/merch/ShoppingCart.css @@ -108,18 +108,20 @@ .cart-continue-button { padding: 0.75rem 1.5rem; - background: var(--ifm-color-primary); + background: linear-gradient(135deg, #667eea 0%, #5b47d6 100%); color: white; border: none; border-radius: 8px; font-weight: 600; cursor: pointer; transition: all 0.2s ease; + box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); } .cart-continue-button:hover { - background: var(--ifm-color-primary-dark); - transform: translateY(-2px); + background: linear-gradient(135deg, #818cf8 0%, #06b6d4 100%); + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); } /* Cart Items */ @@ -173,7 +175,7 @@ .cart-item-price { font-size: 0.875rem; font-weight: 700; - color: var(--ifm-color-primary); + color: #5b47d6; margin: 0; } @@ -209,7 +211,7 @@ } .quantity-controls button:hover:not(:disabled) { - background: var(--ifm-color-primary); + background: linear-gradient(135deg, #667eea 0%, #5b47d6 100%); color: white; } @@ -298,14 +300,14 @@ border-top: 2px solid var(--ifm-color-emphasis-200); font-size: 1.125rem; font-weight: 700; - color: var(--ifm-color-primary); + color: #5b47d6; } /* Checkout Button */ .cart-checkout-button { width: 100%; padding: 1rem; - background: var(--ifm-color-primary); + background: linear-gradient(135deg, #667eea 0%, #5b47d6 100%); color: white; border: none; border-radius: 8px; @@ -318,12 +320,17 @@ gap: 0.5rem; transition: all 0.2s ease; margin-bottom: 0.75rem; + box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); } .cart-checkout-button:hover { - background: var(--ifm-color-primary-dark); - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); + background: linear-gradient(135deg, #818cf8 0%, #06b6d4 100%); + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); +} + +.cart-checkout-button:active { + transform: translateY(0); } .cart-secure-text { diff --git a/src/pages/merch/index.tsx b/src/pages/merch/index.tsx index 2ec6eb77..e1a8de5e 100644 --- a/src/pages/merch/index.tsx +++ b/src/pages/merch/index.tsx @@ -229,62 +229,64 @@ export default function MerchPage(): ReactNode { animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.6 }} > -

- - Official Recode Merch -

-

- Wear your code pride! Premium quality apparel and accessories for - developers who love open source. -

-
-
- - 100% - - Quality -
-
- - 🌍 - - Worldwide Shipping -
-
- - 💚 - - Eco-Friendly +
+

+ Official Recode Merch +

+

+ Wear your code pride! Premium quality apparel and accessories for + developers who love open source. +

+
+
+
+
+ 100% + Quality +
+
+ 🌍 + Worldwide Shipping +
+
+ 🌱 + Eco-Friendly +
- {/* Filter Bar */} - + {/* Main Content Area with Sidebar */} +
+ {/* Sidebar with Filters */} + - {/* Products Grid */} -
- {loading ? ( -
-

- Loading products... -

-
- ) : ( - - )} -
+ {/* Products Grid */} +
+ {loading ? ( +
+

+ Loading products... +

+
+ ) : ( + + )} +
+
{/* Shopping Cart */}
Free shipping on orders over $50

30-day return policy, no questions asked

Eco-friendly materials and ethical production