boutique / index.html
RadMann's picture
undefined - Initial Deployment
fabe0d8 verified
<!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 -->
<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>
<!-- Panier (dropdown) -->
<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>
<!-- Filtres par catégorie -->
<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>
<!-- Liste des produits -->
<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>
<!-- Empty state -->
<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
});
}
// Petite animation de feedback
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>