|
|
<!DOCTYPE html> |
|
|
<html lang="fr"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Boutique en ligne</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> |
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
|
<style> |
|
|
.fade-enter-active, .fade-leave-active { |
|
|
transition: opacity 0.3s ease; |
|
|
} |
|
|
.fade-enter-from, .fade-leave-to { |
|
|
opacity: 0; |
|
|
} |
|
|
.card-hover { |
|
|
transition: transform 0.2s ease, box-shadow 0.2s ease; |
|
|
} |
|
|
.card-hover:hover { |
|
|
transform: translateY(-2px); |
|
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); |
|
|
} |
|
|
.badge-hover { |
|
|
transition: background-color 0.2s ease; |
|
|
} |
|
|
.badge-hover:hover { |
|
|
background-color: #3b82f6; |
|
|
color: white; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="bg-gray-50 min-h-screen"> |
|
|
<div id="app" class="container mx-auto px-4 py-8"> |
|
|
|
|
|
<header class="flex justify-between items-center mb-8"> |
|
|
<div> |
|
|
<h1 class="text-3xl font-bold text-gray-800">Boutique en ligne</h1> |
|
|
<p class="text-gray-600">Découvrez nos produits exceptionnels</p> |
|
|
</div> |
|
|
<div class="relative"> |
|
|
<button @click="toggleCart" class="flex items-center space-x-2 bg-white px-4 py-2 rounded-lg shadow-sm border border-gray-200 hover:bg-gray-50 transition"> |
|
|
<i class="fas fa-shopping-cart text-gray-700"></i> |
|
|
<span class="font-medium">Panier</span> |
|
|
<span v-if="cart.length > 0" class="absolute -top-2 -right-2 bg-blue-500 text-white text-xs font-bold rounded-full h-5 w-5 flex items-center justify-center"> |
|
|
{{ cart.reduce((acc, item) => acc + item.quantity, 0) }} |
|
|
</span> |
|
|
</button> |
|
|
</div> |
|
|
</header> |
|
|
|
|
|
|
|
|
<transition name="fade"> |
|
|
<div v-if="showCart" class="absolute right-4 mt-2 w-80 bg-white rounded-lg shadow-lg z-10 border border-gray-200 overflow-hidden"> |
|
|
<div class="p-4 border-b border-gray-200"> |
|
|
<div class="flex justify-between items-center"> |
|
|
<h3 class="font-bold text-lg">Votre panier</h3> |
|
|
<button @click="toggleCart" class="text-gray-500 hover:text-gray-700"> |
|
|
<i class="fas fa-times"></i> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
<div class="max-h-96 overflow-y-auto"> |
|
|
<div v-if="cart.length === 0" class="p-4 text-center text-gray-500"> |
|
|
Votre panier est vide |
|
|
</div> |
|
|
<div v-for="item in cart" :key="item.id" class="p-4 border-b border-gray-200 last:border-b-0 flex justify-between items-center"> |
|
|
<div class="flex items-center space-x-3"> |
|
|
<div class="w-12 h-12 bg-gray-100 rounded-md flex items-center justify-center"> |
|
|
<i :class="item.icon" class="text-gray-500"></i> |
|
|
</div> |
|
|
<div> |
|
|
<h4 class="font-medium">{{ item.name }}</h4> |
|
|
<p class="text-sm text-gray-500">{{ item.price }} € x {{ item.quantity }}</p> |
|
|
</div> |
|
|
</div> |
|
|
<div class="flex items-center space-x-2"> |
|
|
<button @click="decreaseQuantity(item.id)" class="w-6 h-6 flex items-center justify-center bg-gray-100 rounded hover:bg-gray-200"> |
|
|
<i class="fas fa-minus text-xs"></i> |
|
|
</button> |
|
|
<button @click="increaseQuantity(item.id)" class="w-6 h-6 flex items-center justify-center bg-gray-100 rounded hover:bg-gray-200"> |
|
|
<i class="fas fa-plus text-xs"></i> |
|
|
</button> |
|
|
<button @click="removeFromCart(item.id)" class="w-6 h-6 flex items-center justify-center bg-red-100 rounded hover:bg-red-200 text-red-500"> |
|
|
<i class="fas fa-trash text-xs"></i> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div v-if="cart.length > 0" class="p-4 bg-gray-50"> |
|
|
<div class="flex justify-between mb-2"> |
|
|
<span class="font-medium">Total</span> |
|
|
<span class="font-bold">{{ totalPrice }} €</span> |
|
|
</div> |
|
|
<button class="w-full bg-blue-500 hover:bg-blue-600 text-white py-2 rounded-lg font-medium transition"> |
|
|
Passer la commande |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</transition> |
|
|
|
|
|
|
|
|
<div class="mb-8"> |
|
|
<h2 class="text-lg font-medium text-gray-700 mb-3">Filtrer par catégorie</h2> |
|
|
<div class="flex flex-wrap gap-2"> |
|
|
<button |
|
|
v-for="category in categories" |
|
|
:key="category.id" |
|
|
@click="setActiveCategory(category.id)" |
|
|
:class="[ |
|
|
'px-4 py-2 rounded-full text-sm font-medium border border-gray-200 badge-hover', |
|
|
activeCategory === category.id ? 'bg-blue-500 text-white border-blue-500' : 'bg-white text-gray-700' |
|
|
]" |
|
|
> |
|
|
{{ category.name }} |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6"> |
|
|
<div |
|
|
v-for="product in filteredProducts" |
|
|
:key="product.id" |
|
|
class="bg-white rounded-xl overflow-hidden shadow-sm border border-gray-200 card-hover" |
|
|
> |
|
|
<div class="h-48 bg-gray-100 flex items-center justify-center"> |
|
|
<i :class="product.icon" class="text-5xl text-gray-400"></i> |
|
|
</div> |
|
|
<div class="p-4"> |
|
|
<div class="flex justify-between items-start mb-2"> |
|
|
<h3 class="font-bold text-gray-800">{{ product.name }}</h3> |
|
|
<span class="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">{{ product.category }}</span> |
|
|
</div> |
|
|
<p class="text-gray-600 text-sm mb-3">{{ product.description }}</p> |
|
|
<div class="flex justify-between items-center"> |
|
|
<span class="font-bold text-gray-900">{{ product.price }} €</span> |
|
|
<button |
|
|
@click="addToCart(product)" |
|
|
class="px-3 py-1 bg-blue-500 hover:bg-blue-600 text-white rounded-lg text-sm font-medium transition flex items-center space-x-1" |
|
|
> |
|
|
<i class="fas fa-cart-plus text-xs"></i> |
|
|
<span>Ajouter</span> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div v-if="filteredProducts.length === 0" class="text-center py-12"> |
|
|
<i class="fas fa-box-open text-5xl text-gray-300 mb-4"></i> |
|
|
<h3 class="text-xl font-medium text-gray-700 mb-2">Aucun produit trouvé</h3> |
|
|
<p class="text-gray-500">Essayez de changer vos filtres de recherche</p> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
const { createApp, ref, computed } = Vue; |
|
|
|
|
|
createApp({ |
|
|
setup() { |
|
|
const showCart = ref(false); |
|
|
const activeCategory = ref('all'); |
|
|
|
|
|
const categories = ref([ |
|
|
{ id: 'all', name: 'Tous les produits' }, |
|
|
{ id: 'electronics', name: 'Électronique' }, |
|
|
{ id: 'clothing', name: 'Vêtements' }, |
|
|
{ id: 'home', name: 'Maison' }, |
|
|
{ id: 'sports', name: 'Sports' }, |
|
|
{ id: 'books', name: 'Livres' } |
|
|
]); |
|
|
|
|
|
const products = ref([ |
|
|
{ |
|
|
id: 1, |
|
|
name: 'Smartphone Premium', |
|
|
description: 'Écran 6.7", 128Go, triple caméra', |
|
|
price: 799, |
|
|
category: 'electronics', |
|
|
icon: 'fas fa-mobile-alt' |
|
|
}, |
|
|
{ |
|
|
id: 2, |
|
|
name: 'Casque sans fil', |
|
|
description: 'Réduction de bruit active, 30h autonomie', |
|
|
price: 199, |
|
|
category: 'electronics', |
|
|
icon: 'fas fa-headphones' |
|
|
}, |
|
|
{ |
|
|
id: 3, |
|
|
name: 'T-shirt coton', |
|
|
description: '100% coton bio, plusieurs coloris', |
|
|
price: 29, |
|
|
category: 'clothing', |
|
|
icon: 'fas fa-tshirt' |
|
|
}, |
|
|
{ |
|
|
id: 4, |
|
|
name: 'Lampadaire design', |
|
|
description: 'Hauteur 1m80, métal et verre', |
|
|
price: 149, |
|
|
category: 'home', |
|
|
icon: 'fas fa-lightbulb' |
|
|
}, |
|
|
{ |
|
|
id: 5, |
|
|
name: 'Vélo de course', |
|
|
description: 'Cadre carbone, 21 vitesses', |
|
|
price: 1299, |
|
|
category: 'sports', |
|
|
icon: 'fas fa-bicycle' |
|
|
}, |
|
|
{ |
|
|
id: 6, |
|
|
name: 'Roman best-seller', |
|
|
description: 'Édition limitée, 400 pages', |
|
|
price: 19, |
|
|
category: 'books', |
|
|
icon: 'fas fa-book' |
|
|
}, |
|
|
{ |
|
|
id: 7, |
|
|
name: 'Montre connectée', |
|
|
description: 'Suivi santé, écran AMOLED', |
|
|
price: 249, |
|
|
category: 'electronics', |
|
|
icon: 'fas fa-clock' |
|
|
}, |
|
|
{ |
|
|
id: 8, |
|
|
name: 'Coussin décoratif', |
|
|
description: '45x45cm, plusieurs motifs', |
|
|
price: 39, |
|
|
category: 'home', |
|
|
icon: 'fas fa-couch' |
|
|
} |
|
|
]); |
|
|
|
|
|
const cart = ref([]); |
|
|
|
|
|
const filteredProducts = computed(() => { |
|
|
if (activeCategory.value === 'all') { |
|
|
return products.value; |
|
|
} |
|
|
return products.value.filter(product => product.category === activeCategory.value); |
|
|
}); |
|
|
|
|
|
const totalPrice = computed(() => { |
|
|
return cart.value.reduce((total, item) => total + (item.price * item.quantity), 0); |
|
|
}); |
|
|
|
|
|
const toggleCart = () => { |
|
|
showCart.value = !showCart.value; |
|
|
}; |
|
|
|
|
|
const setActiveCategory = (categoryId) => { |
|
|
activeCategory.value = categoryId; |
|
|
}; |
|
|
|
|
|
const addToCart = (product) => { |
|
|
const existingItem = cart.value.find(item => item.id === product.id); |
|
|
|
|
|
if (existingItem) { |
|
|
existingItem.quantity += 1; |
|
|
} else { |
|
|
cart.value.push({ |
|
|
...product, |
|
|
quantity: 1 |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
const button = event.target.closest('button'); |
|
|
if (button) { |
|
|
button.classList.add('bg-green-500'); |
|
|
setTimeout(() => { |
|
|
button.classList.remove('bg-green-500'); |
|
|
}, 300); |
|
|
} |
|
|
}; |
|
|
|
|
|
const removeFromCart = (productId) => { |
|
|
cart.value = cart.value.filter(item => item.id !== productId); |
|
|
}; |
|
|
|
|
|
const increaseQuantity = (productId) => { |
|
|
const item = cart.value.find(item => item.id === productId); |
|
|
if (item) item.quantity += 1; |
|
|
}; |
|
|
|
|
|
const decreaseQuantity = (productId) => { |
|
|
const item = cart.value.find(item => item.id === productId); |
|
|
if (item) { |
|
|
if (item.quantity > 1) { |
|
|
item.quantity -= 1; |
|
|
} else { |
|
|
removeFromCart(productId); |
|
|
} |
|
|
} |
|
|
}; |
|
|
|
|
|
return { |
|
|
showCart, |
|
|
activeCategory, |
|
|
categories, |
|
|
products, |
|
|
cart, |
|
|
filteredProducts, |
|
|
totalPrice, |
|
|
toggleCart, |
|
|
setActiveCategory, |
|
|
addToCart, |
|
|
removeFromCart, |
|
|
increaseQuantity, |
|
|
decreaseQuantity |
|
|
}; |
|
|
} |
|
|
}).mount('#app'); |
|
|
</script> |
|
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=RadMann/boutique" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
|
</html> |