diff --git a/app/templates/shared/macros/shop/add-to-cart.html b/app/templates/shared/macros/shop/add-to-cart.html
new file mode 100644
index 00000000..7d35132e
--- /dev/null
+++ b/app/templates/shared/macros/shop/add-to-cart.html
@@ -0,0 +1,209 @@
+{#
+ Add to Cart Components
+ ======================
+ Standardized add-to-cart functionality with quantity selector.
+
+ Usage:
+ {% from 'shared/macros/shop/add-to-cart.html' import add_to_cart_button, add_to_cart_form %}
+#}
+
+
+{#
+ Add to Cart Button
+ ==================
+ Simple add to cart button with loading state.
+
+ Parameters:
+ - product_id_var: Alpine.js expression for product ID (default: 'product.id')
+ - variant_id_var: Alpine.js expression for variant ID (default: 'selectedVariant?.id')
+ - quantity_var: Alpine.js variable for quantity (default: 'quantity')
+ - loading_var: Alpine.js variable for loading state (default: 'addingToCart')
+ - stock_var: Alpine.js expression for stock (default: 'product.stock')
+ - action: Alpine.js action to execute (default: 'addToCart()')
+ - size: 'sm' | 'md' | 'lg' (default: 'md')
+ - full_width: Make button full width (default: true)
+ - show_icon: Show cart icon (default: true)
+ - label: Button label (default: 'Add to Cart')
+ - loading_label: Label while loading (default: 'Adding...')
+
+ Usage:
+ {{ add_to_cart_button() }}
+ {{ add_to_cart_button(size='lg', label='Buy Now') }}
+#}
+{% macro add_to_cart_button(
+ product_id_var='product.id',
+ variant_id_var='selectedVariant?.id',
+ quantity_var='quantity',
+ loading_var='addingToCart',
+ stock_var='product.stock',
+ action='addToCart()',
+ size='md',
+ full_width=true,
+ show_icon=true,
+ label='Add to Cart',
+ loading_label='Adding...'
+) %}
+{% set sizes = {
+ 'sm': {'btn': 'px-3 py-1.5 text-sm', 'icon': 'w-4 h-4'},
+ 'md': {'btn': 'px-4 py-2.5 text-base', 'icon': 'w-5 h-5'},
+ 'lg': {'btn': 'px-6 py-3 text-lg', 'icon': 'w-6 h-6'}
+} %}
+{% set s = sizes[size] %}
+
+
+{% endmacro %}
+
+
+{#
+ Add to Cart Form
+ ================
+ Complete add to cart section with quantity selector.
+
+ Parameters:
+ - product_var: Alpine.js variable for product (default: 'product')
+ - quantity_var: Alpine.js variable for quantity (default: 'quantity')
+ - loading_var: Alpine.js variable for loading state (default: 'addingToCart')
+ - action: Alpine.js action to execute (default: 'addToCart()')
+ - show_stock: Show stock status (default: true)
+ - size: 'sm' | 'md' | 'lg' (default: 'md')
+
+ Usage:
+ {{ add_to_cart_form() }}
+#}
+{% macro add_to_cart_form(
+ product_var='product',
+ quantity_var='quantity',
+ loading_var='addingToCart',
+ action='addToCart()',
+ show_stock=true,
+ size='md'
+) %}
+{% from 'shared/macros/inputs.html' import number_stepper %}
+
+
+ {# Stock Status #}
+ {% if show_stock %}
+
+
+
+
+
+
+ {% endif %}
+
+ {# Quantity and Add Button #}
+
+ {# Quantity Selector #}
+
+
+ {{ number_stepper(model=quantity_var, min=1, max=product_var ~ '.stock', size=size, disabled_var=loading_var) }}
+
+
+ {# Add to Cart Button #}
+
+
+ {{ add_to_cart_button(
+ stock_var=product_var ~ '.stock',
+ loading_var=loading_var,
+ action=action,
+ size=size,
+ full_width=true
+ ) }}
+
+
+
+ {# Success Message #}
+
+
+
Added to cart!
+
View Cart
+
+
+{% endmacro %}
+
+
+{#
+ Buy Now Button
+ ==============
+ Direct checkout button (skips cart).
+
+ Parameters:
+ - action: Alpine.js action (default: 'buyNow()')
+ - loading_var: Loading state variable (default: 'buyingNow')
+ - stock_var: Stock expression (default: 'product.stock')
+ - size: 'sm' | 'md' | 'lg' (default: 'md')
+ - full_width: Full width button (default: true)
+
+ Usage:
+ {{ buy_now_button() }}
+#}
+{% macro buy_now_button(
+ action='buyNow()',
+ loading_var='buyingNow',
+ stock_var='product.stock',
+ size='md',
+ full_width=true
+) %}
+{% set sizes = {
+ 'sm': {'btn': 'px-3 py-1.5 text-sm', 'icon': 'w-4 h-4'},
+ 'md': {'btn': 'px-4 py-2.5 text-base', 'icon': 'w-5 h-5'},
+ 'lg': {'btn': 'px-6 py-3 text-lg', 'icon': 'w-6 h-6'}
+} %}
+{% set s = sizes[size] %}
+
+
+{% endmacro %}
+
+
+{#
+ Quantity Selector (Shop variant)
+ ================================
+ Shop-specific quantity selector with stock validation.
+
+ Parameters:
+ - model: Alpine.js model for quantity (required)
+ - max_var: Alpine.js expression for max stock (default: 'product.stock')
+ - disabled_var: Alpine.js variable for disabled state (default: none)
+ - size: 'sm' | 'md' | 'lg' (default: 'md')
+
+ Usage:
+ {{ shop_quantity_selector(model='quantity', max_var='product.stock') }}
+#}
+{% macro shop_quantity_selector(model, max_var='product.stock', disabled_var=none, size='md') %}
+{% from 'shared/macros/inputs.html' import number_stepper %}
+{{ number_stepper(model=model, min=1, max=max_var, size=size, disabled_var=disabled_var, label='Quantity') }}
+{% endmacro %}
diff --git a/app/templates/shared/macros/shop/mini-cart.html b/app/templates/shared/macros/shop/mini-cart.html
new file mode 100644
index 00000000..ba64289c
--- /dev/null
+++ b/app/templates/shared/macros/shop/mini-cart.html
@@ -0,0 +1,408 @@
+{#
+ Mini Cart Components
+ ====================
+ Cart preview dropdown and cart item components.
+
+ Usage:
+ {% from 'shared/macros/shop/mini-cart.html' import mini_cart, cart_icon_button, cart_item %}
+#}
+
+
+{#
+ Cart Icon Button
+ ================
+ Cart icon with item count badge for header.
+
+ Parameters:
+ - cart_count_var: Alpine.js expression for cart item count (default: 'cart.items.length')
+ - toggle_action: Alpine.js action to toggle cart dropdown (default: 'toggleCart()')
+ - size: 'sm' | 'md' | 'lg' (default: 'md')
+
+ Usage:
+ {{ cart_icon_button() }}
+#}
+{% macro cart_icon_button(
+ cart_count_var='cart.items.length',
+ toggle_action='toggleCart()',
+ size='md'
+) %}
+{% set sizes = {
+ 'sm': {'btn': 'p-1.5', 'icon': 'w-5 h-5', 'badge': 'w-4 h-4 text-xs -top-1 -right-1'},
+ 'md': {'btn': 'p-2', 'icon': 'w-6 h-6', 'badge': 'w-5 h-5 text-xs -top-1.5 -right-1.5'},
+ 'lg': {'btn': 'p-2.5', 'icon': 'w-7 h-7', 'badge': 'w-6 h-6 text-sm -top-2 -right-2'}
+} %}
+{% set s = sizes[size] %}
+
+
+{% endmacro %}
+
+
+{#
+ Mini Cart Dropdown
+ ==================
+ Cart preview dropdown showing recent items.
+
+ Parameters:
+ - cart_var: Alpine.js variable for cart object (default: 'cart')
+ - show_var: Alpine.js variable for dropdown visibility (default: 'showCart')
+ - max_items: Maximum items to show (default: 3)
+ - cart_url: URL to full cart page (default: '/cart')
+ - checkout_url: URL to checkout (default: '/checkout')
+
+ Expected cart object structure:
+ {
+ items: [
+ {id, product_id, name, image_url, price, quantity, variant_name}
+ ],
+ subtotal: number,
+ item_count: number
+ }
+
+ Usage:
+ {{ mini_cart() }}
+#}
+{% macro mini_cart(
+ cart_var='cart',
+ show_var='showCart',
+ max_items=3,
+ cart_url='/cart',
+ checkout_url='/checkout'
+) %}
+
+ {# Header #}
+
+
+ {# Empty State #}
+
+
+
+
+
Your cart is empty
+
+ Continue Shopping
+
+
+
+ {# Cart Items #}
+
+
+ {{ cart_item_mini(cart_var=cart_var) }}
+
+
+ {# More Items Notice #}
+
+
+
+
+
+ {# Footer #}
+
+ {# Subtotal #}
+
+ Subtotal
+
+
+
+ {# Actions #}
+
+
+
+{% endmacro %}
+
+
+{#
+ Cart Item (Mini version)
+ ========================
+ Compact cart item for mini cart dropdown.
+
+ Parameters:
+ - cart_var: Cart variable for remove action (default: 'cart')
+
+ Usage:
+
+ {{ cart_item_mini() }}
+
+#}
+{% macro cart_item_mini(cart_var='cart') %}
+
+ {# Image #}
+
+
+
+
+ {# Details #}
+
+
+ {# Remove Button #}
+
+
+{% endmacro %}
+
+
+{#
+ Cart Item (Full version)
+ ========================
+ Full cart item for cart page with quantity controls.
+
+ Parameters:
+ - item_var: Variable name for item (default: 'item')
+ - index_var: Variable name for index (default: 'index')
+ - show_image: Show product image (default: true)
+ - editable: Allow quantity editing (default: true)
+
+ Usage:
+
+ {{ cart_item() }}
+
+#}
+{% macro cart_item(
+ item_var='item',
+ index_var='index',
+ show_image=true,
+ editable=true
+) %}
+{% from 'shared/macros/inputs.html' import number_stepper %}
+
+
+ {# Image #}
+ {% if show_image %}
+
+
+
+ {% endif %}
+
+ {# Details #}
+
+
+
+
+ {# Remove Button #}
+
+
+
+ {# Quantity and Total #}
+
+ {% if editable %}
+
+ {{ number_stepper(
+ model=item_var ~ '.quantity',
+ min=1,
+ max=item_var ~ '.max_quantity',
+ size='sm',
+ label='Quantity'
+ ) }}
+
+ {% else %}
+
+ {% endif %}
+
+
+
+
+ {# Low Stock Warning #}
+
+
+
+
+
+
+{% endmacro %}
+
+
+{#
+ Cart Summary
+ ============
+ Order summary with totals and checkout button.
+
+ Parameters:
+ - cart_var: Alpine.js variable for cart (default: 'cart')
+ - show_promo: Show promo code input (default: true)
+ - show_shipping: Show shipping estimate (default: true)
+ - checkout_url: Checkout URL (default: '/checkout')
+
+ Expected cart structure:
+ {
+ subtotal: number,
+ discount: number,
+ shipping: number,
+ tax: number,
+ total: number,
+ promo_code: string | null
+ }
+
+ Usage:
+ {{ cart_summary() }}
+#}
+{% macro cart_summary(
+ cart_var='cart',
+ show_promo=true,
+ show_shipping=true,
+ checkout_url='/checkout'
+) %}
+
+
Order Summary
+
+ {# Promo Code #}
+ {% if show_promo %}
+
+
+
+
+
+
+
+
+ {% endif %}
+
+ {# Totals #}
+
+
+ Subtotal
+
+
+
+
+ Discount
+
+
+
+ {% if show_shipping %}
+
+ Shipping
+
+
+ {% endif %}
+
+
+ Tax
+
+
+
+
+
+
+ {# Checkout Button #}
+
+
+{% endmacro %}
diff --git a/app/templates/shared/macros/shop/product-card.html b/app/templates/shared/macros/shop/product-card.html
new file mode 100644
index 00000000..b51141df
--- /dev/null
+++ b/app/templates/shared/macros/shop/product-card.html
@@ -0,0 +1,253 @@
+{#
+ Product Card
+ ============
+ A versatile product card component for e-commerce listings.
+ Supports multiple sizes, badges, ratings, and quick actions.
+
+ Parameters:
+ - product_var: Alpine.js variable name for the product object (default: 'product')
+ - size: 'sm' | 'md' | 'lg' (default: 'md')
+ - show_rating: Show star rating (default: true)
+ - show_quick_add: Show quick add to cart button (default: true)
+ - show_wishlist: Show wishlist heart icon (default: true)
+ - show_vendor: Show vendor name for marketplace (default: false)
+ - add_to_cart_action: Alpine.js action for add to cart (default: 'addToCart(product)')
+ - wishlist_action: Alpine.js action for wishlist toggle (default: 'toggleWishlist(product)')
+ - product_url_field: Field name for product URL (default: 'url')
+ - image_field: Field name for image URL (default: 'image_url')
+ - title_field: Field name for product title (default: 'name')
+ - price_field: Field name for price (default: 'price')
+ - sale_price_field: Field name for sale price (default: 'sale_price')
+ - rating_field: Field name for rating (default: 'rating')
+ - review_count_field: Field name for review count (default: 'review_count')
+ - stock_field: Field name for stock quantity (default: 'stock')
+ - vendor_field: Field name for vendor name (default: 'vendor_name')
+
+ Expected product object structure:
+ {
+ id: number,
+ name: string,
+ url: string,
+ image_url: string,
+ price: number,
+ sale_price: number | null,
+ rating: number (0-5),
+ review_count: number,
+ stock: number,
+ is_new: boolean,
+ vendor_name: string (optional)
+ }
+
+ Usage:
+ {% from 'shared/macros/shop/product-card.html' import product_card %}
+
+
+ {{ product_card() }}
+
+
+ {# With custom settings #}
+ {{ product_card(product_var='featuredProduct', size='lg', show_vendor=true) }}
+#}
+
+{% macro product_card(
+ product_var='product',
+ size='md',
+ show_rating=true,
+ show_quick_add=true,
+ show_wishlist=true,
+ show_vendor=false,
+ add_to_cart_action='addToCart(product)',
+ wishlist_action='toggleWishlist(product)',
+ product_url_field='url',
+ image_field='image_url',
+ title_field='name',
+ price_field='price',
+ sale_price_field='sale_price',
+ rating_field='rating',
+ review_count_field='review_count',
+ stock_field='stock',
+ vendor_field='vendor_name'
+) %}
+{% set sizes = {
+ 'sm': {
+ 'card': 'max-w-[200px]',
+ 'image': 'h-32',
+ 'title': 'text-sm',
+ 'price': 'text-sm',
+ 'badge': 'text-xs px-1.5 py-0.5',
+ 'btn': 'text-xs px-2 py-1',
+ 'icon': 'w-4 h-4',
+ 'rating': 'w-3 h-3'
+ },
+ 'md': {
+ 'card': 'max-w-[280px]',
+ 'image': 'h-48',
+ 'title': 'text-base',
+ 'price': 'text-base',
+ 'badge': 'text-xs px-2 py-1',
+ 'btn': 'text-sm px-3 py-2',
+ 'icon': 'w-5 h-5',
+ 'rating': 'w-4 h-4'
+ },
+ 'lg': {
+ 'card': 'max-w-[360px]',
+ 'image': 'h-64',
+ 'title': 'text-lg',
+ 'price': 'text-lg',
+ 'badge': 'text-sm px-2.5 py-1',
+ 'btn': 'text-base px-4 py-2.5',
+ 'icon': 'w-6 h-6',
+ 'rating': 'w-5 h-5'
+ }
+} %}
+{% set s = sizes[size] %}
+
+
+ {# Image Container #}
+
+
+
+
+
+ {# Badges #}
+
+ {# Sale Badge #}
+
+
+
+ {# New Badge #}
+
+ New
+
+
+
+ {# Wishlist Button #}
+ {% if show_wishlist %}
+
+ {% endif %}
+
+ {# Out of Stock Overlay #}
+
+ Out of Stock
+
+
+
+ {# Content #}
+
+ {# Vendor Name #}
+ {% if show_vendor %}
+
+ {% endif %}
+
+ {# Title #}
+
+
+
+
+ {# Rating #}
+ {% if show_rating %}
+
+ {% endif %}
+
+ {# Price #}
+
+
+
+
+
+ {# Quick Add Button #}
+ {% if show_quick_add %}
+
+ {% endif %}
+
+
+{% endmacro %}
+
+
+{#
+ Product Badge
+ =============
+ Standalone badge component for product overlays.
+
+ Parameters:
+ - type: 'sale' | 'new' | 'bestseller' | 'low_stock' | 'out_of_stock'
+ - value: Optional value (e.g., '-20%' for sale, 'Only 3 left' for low_stock)
+ - size: 'sm' | 'md' | 'lg' (default: 'md')
+
+ Usage:
+ {{ product_badge(type='sale', value='-20%') }}
+ {{ product_badge(type='new') }}
+ {{ product_badge(type='low_stock', value='Only 3 left') }}
+#}
+{% macro product_badge(type, value=none, size='md') %}
+{% set badge_styles = {
+ 'sale': 'bg-red-500 text-white',
+ 'new': 'bg-green-500 text-white',
+ 'bestseller': 'bg-yellow-500 text-gray-900',
+ 'low_stock': 'bg-orange-500 text-white',
+ 'out_of_stock': 'bg-gray-700 text-white'
+} %}
+{% set badge_labels = {
+ 'sale': 'Sale',
+ 'new': 'New',
+ 'bestseller': 'Bestseller',
+ 'low_stock': 'Low Stock',
+ 'out_of_stock': 'Out of Stock'
+} %}
+{% set sizes = {
+ 'sm': 'text-xs px-1.5 py-0.5',
+ 'md': 'text-xs px-2 py-1',
+ 'lg': 'text-sm px-2.5 py-1'
+} %}
+
+ {{ value if value else badge_labels[type] }}
+
+{% endmacro %}
diff --git a/app/templates/shared/macros/shop/product-grid.html b/app/templates/shared/macros/shop/product-grid.html
new file mode 100644
index 00000000..d7a29e7a
--- /dev/null
+++ b/app/templates/shared/macros/shop/product-grid.html
@@ -0,0 +1,215 @@
+{#
+ Product Grid
+ ============
+ Responsive grid layout for product listings.
+ Includes loading skeletons and empty state.
+
+ Parameters:
+ - products_var: Alpine.js variable containing products array (default: 'products')
+ - loading_var: Alpine.js variable for loading state (default: 'loading')
+ - columns: Dict with breakpoint column counts (default: {sm: 2, md: 3, lg: 4})
+ - gap: Gap size 'sm' | 'md' | 'lg' (default: 'md')
+ - card_size: Product card size 'sm' | 'md' | 'lg' (default: 'md')
+ - show_rating: Pass to product cards (default: true)
+ - show_quick_add: Pass to product cards (default: true)
+ - show_wishlist: Pass to product cards (default: true)
+ - show_vendor: Pass to product cards (default: false)
+ - empty_title: Title for empty state (default: 'No products found')
+ - empty_message: Message for empty state (default: 'Try adjusting your filters')
+ - empty_icon: Icon for empty state (default: 'shopping-bag')
+
+ Usage:
+ {% from 'shared/macros/shop/product-grid.html' import product_grid, product_grid_skeleton %}
+
+ {{ product_grid() }}
+
+ {# With custom columns #}
+ {{ product_grid(columns={'sm': 1, 'md': 2, 'lg': 3}) }}
+#}
+
+{% macro product_grid(
+ products_var='products',
+ loading_var='loading',
+ columns=none,
+ gap='md',
+ card_size='md',
+ show_rating=true,
+ show_quick_add=true,
+ show_wishlist=true,
+ show_vendor=false,
+ empty_title='No products found',
+ empty_message='Try adjusting your filters or search terms',
+ empty_icon='shopping-bag'
+) %}
+{% from 'shared/macros/shop/product-card.html' import product_card %}
+
+{% set cols = columns if columns else {'sm': 2, 'md': 3, 'lg': 4} %}
+{% set gaps = {'sm': 'gap-3', 'md': 'gap-4', 'lg': 'gap-6'} %}
+
+{# Loading State #}
+
+
+ {{ product_skeleton(size=card_size) }}
+
+
+
+{# Empty State #}
+
+
+
+
+
{{ empty_title }}
+
{{ empty_message }}
+
+
+{# Product Grid #}
+
+
+ {{ product_card(
+ product_var='product',
+ size=card_size,
+ show_rating=show_rating,
+ show_quick_add=show_quick_add,
+ show_wishlist=show_wishlist,
+ show_vendor=show_vendor
+ ) }}
+
+
+{% endmacro %}
+
+
+{#
+ Product Skeleton
+ ================
+ Loading skeleton for a single product card.
+
+ Parameters:
+ - size: 'sm' | 'md' | 'lg' (default: 'md')
+
+ Usage:
+ {{ product_skeleton() }}
+#}
+{% macro product_skeleton(size='md') %}
+{% set sizes = {
+ 'sm': {'card': 'max-w-[200px]', 'image': 'h-32'},
+ 'md': {'card': 'max-w-[280px]', 'image': 'h-48'},
+ 'lg': {'card': 'max-w-[360px]', 'image': 'h-64'}
+} %}
+{% set s = sizes[size] %}
+
+
+ {# Image Skeleton #}
+
+
+ {# Content Skeleton #}
+
+ {# Title #}
+
+
+
+ {# Rating #}
+
+
+ {# Price #}
+
+
+ {# Button #}
+
+
+
+{% endmacro %}
+
+
+{#
+ Product List Item
+ =================
+ Horizontal product card for list views.
+
+ Parameters:
+ - product_var: Alpine.js variable name (default: 'product')
+ - show_rating: Show star rating (default: true)
+ - show_quick_add: Show add to cart button (default: true)
+ - compact: Use compact spacing (default: false)
+
+ Usage:
+
+ {{ product_list_item() }}
+
+#}
+{% macro product_list_item(
+ product_var='product',
+ show_rating=true,
+ show_quick_add=true,
+ compact=false
+) %}
+
+ {# Image #}
+
+
+
+
+ {# Content #}
+
+
+
+
+
+ {% if show_rating %}
+
+ {% endif %}
+
+
+
+
+
+
+
+ {# Actions #}
+ {% if show_quick_add %}
+
+
+
+ {% endif %}
+
+{% endmacro %}