first commit
This commit is contained in:
commit
2d5f88772a
|
|
@ -0,0 +1,12 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>АвтоДетали Про — Запчасти и жидкости для автомобилей</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"name": "react-vite-tailwind",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"clsx": "2.1.1",
|
||||||
|
"react": "19.2.3",
|
||||||
|
"react-dom": "19.2.3",
|
||||||
|
"tailwind-merge": "3.4.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tailwindcss/vite": "4.1.17",
|
||||||
|
"@types/node": "^22.0.0",
|
||||||
|
"@types/react": "19.2.7",
|
||||||
|
"@types/react-dom": "19.2.3",
|
||||||
|
"@vitejs/plugin-react": "5.1.1",
|
||||||
|
"tailwindcss": "4.1.17",
|
||||||
|
"typescript": "5.9.3",
|
||||||
|
"vite": "7.2.4",
|
||||||
|
"vite-plugin-singlefile": "2.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 174 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 169 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 147 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 117 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 136 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 265 KiB |
|
|
@ -0,0 +1,733 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
const categories = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "Двигатель",
|
||||||
|
image: "/images/cat-engine.jpg",
|
||||||
|
count: 1842,
|
||||||
|
icon: "🔧",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "Масла и жидкости",
|
||||||
|
image: "/images/cat-oil.jpg",
|
||||||
|
count: 634,
|
||||||
|
icon: "🛢️",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "Тормозная система",
|
||||||
|
image: "/images/cat-brakes.jpg",
|
||||||
|
count: 921,
|
||||||
|
icon: "⚙️",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: "Фильтры",
|
||||||
|
image: "/images/cat-filters.jpg",
|
||||||
|
count: 488,
|
||||||
|
icon: "🔩",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: "Подвеска и рулевое",
|
||||||
|
image: "/images/cat-suspension.jpg",
|
||||||
|
count: 1103,
|
||||||
|
icon: "🚗",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: "Электрика",
|
||||||
|
image: "/images/cat-engine.jpg",
|
||||||
|
count: 765,
|
||||||
|
icon: "⚡",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const products = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "Масло моторное Castrol EDGE 5W-40",
|
||||||
|
category: "Масла и жидкости",
|
||||||
|
price: 2890,
|
||||||
|
oldPrice: 3400,
|
||||||
|
rating: 4.8,
|
||||||
|
reviews: 214,
|
||||||
|
badge: "Хит",
|
||||||
|
badgeColor: "bg-red-500",
|
||||||
|
image: "/images/cat-oil.jpg",
|
||||||
|
brand: "Castrol",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "Тормозные колодки Brembo P 85 020",
|
||||||
|
category: "Тормозная система",
|
||||||
|
price: 1650,
|
||||||
|
oldPrice: null,
|
||||||
|
rating: 4.9,
|
||||||
|
reviews: 87,
|
||||||
|
badge: "Новинка",
|
||||||
|
badgeColor: "bg-green-500",
|
||||||
|
image: "/images/cat-brakes.jpg",
|
||||||
|
brand: "Brembo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "Воздушный фильтр Mann C 25 114",
|
||||||
|
category: "Фильтры",
|
||||||
|
price: 490,
|
||||||
|
oldPrice: 620,
|
||||||
|
rating: 4.6,
|
||||||
|
reviews: 132,
|
||||||
|
badge: "Скидка",
|
||||||
|
badgeColor: "bg-orange-500",
|
||||||
|
image: "/images/cat-filters.jpg",
|
||||||
|
brand: "Mann",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: "Амортизатор передний KYB 335804",
|
||||||
|
category: "Подвеска и рулевое",
|
||||||
|
price: 3200,
|
||||||
|
oldPrice: null,
|
||||||
|
rating: 4.7,
|
||||||
|
reviews: 56,
|
||||||
|
badge: null,
|
||||||
|
badgeColor: "",
|
||||||
|
image: "/images/cat-suspension.jpg",
|
||||||
|
brand: "KYB",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: "Антифриз Sintec Lux -40°C (зелёный)",
|
||||||
|
category: "Масла и жидкости",
|
||||||
|
price: 380,
|
||||||
|
oldPrice: 450,
|
||||||
|
rating: 4.5,
|
||||||
|
reviews: 301,
|
||||||
|
badge: "Хит",
|
||||||
|
badgeColor: "bg-red-500",
|
||||||
|
image: "/images/cat-oil.jpg",
|
||||||
|
brand: "Sintec",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: "Ремень ГРМ Gates K015591XS",
|
||||||
|
category: "Двигатель",
|
||||||
|
price: 2100,
|
||||||
|
oldPrice: null,
|
||||||
|
rating: 4.9,
|
||||||
|
reviews: 44,
|
||||||
|
badge: null,
|
||||||
|
badgeColor: "",
|
||||||
|
image: "/images/cat-engine.jpg",
|
||||||
|
brand: "Gates",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
name: "Свечи NGK Iridium IX BKR6EIX (4 шт.)",
|
||||||
|
category: "Двигатель",
|
||||||
|
price: 1280,
|
||||||
|
oldPrice: 1560,
|
||||||
|
rating: 4.8,
|
||||||
|
reviews: 178,
|
||||||
|
badge: "Скидка",
|
||||||
|
badgeColor: "bg-orange-500",
|
||||||
|
image: "/images/cat-engine.jpg",
|
||||||
|
brand: "NGK",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
name: "Масло трансмиссионное Mobil ATF 3309",
|
||||||
|
category: "Масла и жидкости",
|
||||||
|
price: 1740,
|
||||||
|
oldPrice: null,
|
||||||
|
rating: 4.7,
|
||||||
|
reviews: 63,
|
||||||
|
badge: "Новинка",
|
||||||
|
badgeColor: "bg-green-500",
|
||||||
|
image: "/images/cat-oil.jpg",
|
||||||
|
brand: "Mobil",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const brands = [
|
||||||
|
"Castrol", "Bosch", "NGK", "Brembo", "KYB", "Mann", "Gates", "Mobil",
|
||||||
|
"Valeo", "Denso", "Monroe", "Febi",
|
||||||
|
];
|
||||||
|
|
||||||
|
const reviews = [
|
||||||
|
{
|
||||||
|
name: "Алексей К.",
|
||||||
|
date: "12 мая 2025",
|
||||||
|
rating: 5,
|
||||||
|
text: "Заказывал масло и фильтры — пришли быстро, всё оригинальное. Упаковка целая, цена лучшая в городе. Буду заказывать снова!",
|
||||||
|
car: "Toyota Camry 2019",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Мария Д.",
|
||||||
|
date: "3 июня 2025",
|
||||||
|
rating: 5,
|
||||||
|
text: "Очень удобный сайт, нашла нужные тормозные колодки за минуту. Доставка на следующий день, качество отличное.",
|
||||||
|
car: "Kia Rio 2021",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Дмитрий В.",
|
||||||
|
date: "28 апреля 2025",
|
||||||
|
rating: 4,
|
||||||
|
text: "Хороший ассортимент, есть редкие позиции. Немного долго ждал ответа от поддержки, но в итоге всё решили.",
|
||||||
|
car: "BMW 3 Series 2018",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function StarRating({ rating, size = "sm" }: { rating: number; size?: "sm" | "md" }) {
|
||||||
|
const sizeClass = size === "sm" ? "text-xs" : "text-sm";
|
||||||
|
return (
|
||||||
|
<div className={`flex items-center gap-0.5 ${sizeClass}`}>
|
||||||
|
{[1, 2, 3, 4, 5].map((s) => (
|
||||||
|
<span key={s} className={s <= Math.round(rating) ? "text-yellow-400" : "text-gray-300"}>
|
||||||
|
★
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
const [cartCount, setCartCount] = useState(0);
|
||||||
|
const [wishlist, setWishlist] = useState<number[]>([]);
|
||||||
|
const [searchValue, setSearchValue] = useState("");
|
||||||
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||||
|
const [addedToCart, setAddedToCart] = useState<number | null>(null);
|
||||||
|
const [selectedBrand] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const handleAddToCart = (id: number) => {
|
||||||
|
setCartCount((c) => c + 1);
|
||||||
|
setAddedToCart(id);
|
||||||
|
setTimeout(() => setAddedToCart(null), 1500);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleWishlist = (id: number) => {
|
||||||
|
setWishlist((prev) =>
|
||||||
|
prev.includes(id) ? prev.filter((w) => w !== id) : [...prev, id]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredProducts = products.filter((p) => {
|
||||||
|
const matchSearch =
|
||||||
|
searchValue === "" ||
|
||||||
|
p.name.toLowerCase().includes(searchValue.toLowerCase()) ||
|
||||||
|
p.brand.toLowerCase().includes(searchValue.toLowerCase()) ||
|
||||||
|
p.category.toLowerCase().includes(searchValue.toLowerCase());
|
||||||
|
const matchBrand = !selectedBrand || p.brand === selectedBrand;
|
||||||
|
return matchSearch && matchBrand;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50 font-sans">
|
||||||
|
{/* Top bar */}
|
||||||
|
<div className="bg-gray-900 text-gray-300 text-xs py-2">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 flex flex-col sm:flex-row items-center justify-between gap-1">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<span>📞 +7 (800) 555-35-35</span>
|
||||||
|
<span className="hidden sm:inline">|</span>
|
||||||
|
<span className="hidden sm:inline">✉️ info@avtodetal-pro.ru</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<span>🕐 Пн–Пт: 9:00–20:00, Сб–Вс: 10:00–18:00</span>
|
||||||
|
<span className="hidden sm:inline">|</span>
|
||||||
|
<span className="hidden sm:inline text-green-400 font-medium">✅ Бесплатная доставка от 3000₽</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Header */}
|
||||||
|
<header className="bg-white shadow-md sticky top-0 z-50">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 py-3 flex items-center gap-4">
|
||||||
|
{/* Logo */}
|
||||||
|
<div className="flex items-center gap-2 shrink-0">
|
||||||
|
<div className="w-10 h-10 bg-red-600 rounded-lg flex items-center justify-center">
|
||||||
|
<span className="text-white text-xl font-black">А</span>
|
||||||
|
</div>
|
||||||
|
<div className="leading-tight">
|
||||||
|
<div className="text-lg font-black text-gray-900 tracking-tight">АвтоДетали</div>
|
||||||
|
<div className="text-xs text-red-600 font-semibold tracking-wider uppercase">Про</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Search */}
|
||||||
|
<div className="flex-1 hidden sm:flex items-center">
|
||||||
|
<div className="relative w-full max-w-xl">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={searchValue}
|
||||||
|
onChange={(e) => setSearchValue(e.target.value)}
|
||||||
|
placeholder="Поиск по запчастям, маслам, артикулу..."
|
||||||
|
className="w-full border-2 border-gray-200 focus:border-red-500 rounded-l-lg px-4 py-2.5 text-sm outline-none transition-colors"
|
||||||
|
/>
|
||||||
|
<button className="bg-red-600 hover:bg-red-700 text-white px-5 py-2.5 rounded-r-lg text-sm font-medium transition-colors">
|
||||||
|
🔍 Найти
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Actions */}
|
||||||
|
<div className="flex items-center gap-2 ml-auto">
|
||||||
|
<button className="hidden md:flex flex-col items-center text-gray-600 hover:text-red-600 transition-colors p-2">
|
||||||
|
<span className="text-xl">👤</span>
|
||||||
|
<span className="text-xs mt-0.5">Войти</span>
|
||||||
|
</button>
|
||||||
|
<button className="hidden md:flex flex-col items-center text-gray-600 hover:text-red-600 transition-colors p-2 relative">
|
||||||
|
<span className="text-xl">❤️</span>
|
||||||
|
<span className="text-xs mt-0.5">Избранное</span>
|
||||||
|
{wishlist.length > 0 && (
|
||||||
|
<span className="absolute top-1 right-1 bg-red-500 text-white text-[10px] rounded-full w-4 h-4 flex items-center justify-center font-bold">
|
||||||
|
{wishlist.length}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
<button className="flex flex-col items-center text-gray-600 hover:text-red-600 transition-colors p-2 relative">
|
||||||
|
<span className="text-xl">🛒</span>
|
||||||
|
<span className="text-xs mt-0.5 hidden md:block">Корзина</span>
|
||||||
|
{cartCount > 0 && (
|
||||||
|
<span className="absolute top-1 right-1 bg-red-500 text-white text-[10px] rounded-full w-4 h-4 flex items-center justify-center font-bold">
|
||||||
|
{cartCount}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="md:hidden p-2 text-gray-600"
|
||||||
|
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||||
|
>
|
||||||
|
<div className="w-6 flex flex-col gap-1">
|
||||||
|
<span className="block h-0.5 bg-gray-700 rounded"></span>
|
||||||
|
<span className="block h-0.5 bg-gray-700 rounded"></span>
|
||||||
|
<span className="block h-0.5 bg-gray-700 rounded"></span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Nav */}
|
||||||
|
<nav className="hidden md:block bg-red-600">
|
||||||
|
<div className="max-w-7xl mx-auto px-4">
|
||||||
|
<ul className="flex items-center gap-0 text-sm font-medium text-white">
|
||||||
|
{["Каталог", "Масла и жидкости", "Фильтры", "Тормоза", "Подвеска", "Электрика", "Аксессуары", "Акции"].map(
|
||||||
|
(item) => (
|
||||||
|
<li key={item}>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="block px-4 py-3 hover:bg-red-700 transition-colors whitespace-nowrap"
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* Mobile menu */}
|
||||||
|
{mobileMenuOpen && (
|
||||||
|
<div className="md:hidden bg-white border-t px-4 py-3 flex flex-col gap-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={searchValue}
|
||||||
|
onChange={(e) => setSearchValue(e.target.value)}
|
||||||
|
placeholder="Поиск..."
|
||||||
|
className="border border-gray-200 rounded-lg px-3 py-2 text-sm outline-none w-full"
|
||||||
|
/>
|
||||||
|
{["Каталог", "Масла и жидкости", "Фильтры", "Тормоза", "Подвеска", "Электрика", "Акции"].map(
|
||||||
|
(item) => (
|
||||||
|
<a key={item} href="#" className="text-sm text-gray-700 py-1 border-b border-gray-100">
|
||||||
|
{item}
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* Hero */}
|
||||||
|
<section
|
||||||
|
className="relative bg-gray-900 text-white overflow-hidden"
|
||||||
|
style={{ minHeight: 420 }}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="/images/hero-bg.jpg"
|
||||||
|
alt="Автозапчасти"
|
||||||
|
className="absolute inset-0 w-full h-full object-cover opacity-40"
|
||||||
|
/>
|
||||||
|
<div className="relative z-10 max-w-7xl mx-auto px-4 py-16 md:py-24">
|
||||||
|
<div className="max-w-2xl">
|
||||||
|
<div className="inline-block bg-red-600 text-white text-xs font-bold px-3 py-1 rounded-full uppercase tracking-wider mb-4">
|
||||||
|
🔥 Летняя распродажа — скидки до 30%
|
||||||
|
</div>
|
||||||
|
<h1 className="text-3xl md:text-5xl font-black leading-tight mb-4">
|
||||||
|
Запчасти и расходники<br />
|
||||||
|
<span className="text-red-400">для любого автомобиля</span>
|
||||||
|
</h1>
|
||||||
|
<p className="text-gray-300 text-base md:text-lg mb-8">
|
||||||
|
Более 50 000 наименований в наличии. Оригинальные и аналоговые запчасти,
|
||||||
|
масла, фильтры, тормоза и многое другое. Доставка по всей России.
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
<button className="bg-red-600 hover:bg-red-700 text-white font-bold px-6 py-3 rounded-lg transition-colors text-sm md:text-base">
|
||||||
|
Перейти в каталог →
|
||||||
|
</button>
|
||||||
|
<button className="bg-white/10 hover:bg-white/20 border border-white/30 text-white font-semibold px-6 py-3 rounded-lg transition-colors text-sm md:text-base">
|
||||||
|
Подбор по марке авто
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-6 mt-8 text-sm text-gray-300">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-green-400">✔</span> Гарантия подлинности
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-green-400">✔</span> Возврат 30 дней
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-green-400">✔</span> Доставка 1–3 дня
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* VIN Search */}
|
||||||
|
<section className="bg-gray-800 py-5">
|
||||||
|
<div className="max-w-7xl mx-auto px-4">
|
||||||
|
<div className="flex flex-col md:flex-row items-center gap-3">
|
||||||
|
<div className="text-white font-semibold text-sm whitespace-nowrap">
|
||||||
|
🚘 Подбор по VIN:
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-1 max-w-2xl w-full">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Введите VIN номер вашего автомобиля..."
|
||||||
|
className="flex-1 bg-white/10 border border-white/20 text-white placeholder-gray-400 px-4 py-2.5 rounded-l-lg text-sm outline-none focus:border-red-400"
|
||||||
|
/>
|
||||||
|
<button className="bg-red-600 hover:bg-red-700 text-white px-5 py-2.5 rounded-r-lg text-sm font-semibold transition-colors">
|
||||||
|
Найти запчасти
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="text-gray-400 text-xs hidden md:block">
|
||||||
|
Введите 17-значный VIN для точного подбора
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Categories */}
|
||||||
|
<section className="max-w-7xl mx-auto px-4 py-12">
|
||||||
|
<h2 className="text-2xl font-black text-gray-900 mb-6">
|
||||||
|
Категории товаров
|
||||||
|
</h2>
|
||||||
|
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-4">
|
||||||
|
{categories.map((cat) => (
|
||||||
|
<a
|
||||||
|
key={cat.id}
|
||||||
|
href="#"
|
||||||
|
className="group relative bg-white rounded-xl overflow-hidden shadow hover:shadow-lg transition-all border border-gray-100 hover:border-red-200"
|
||||||
|
>
|
||||||
|
<div className="h-28 overflow-hidden">
|
||||||
|
<img
|
||||||
|
src={cat.image}
|
||||||
|
alt={cat.name}
|
||||||
|
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-t from-gray-900/70 to-transparent" />
|
||||||
|
</div>
|
||||||
|
<div className="absolute bottom-0 left-0 right-0 p-2.5">
|
||||||
|
<div className="text-white font-bold text-xs leading-tight">{cat.name}</div>
|
||||||
|
<div className="text-gray-300 text-[10px] mt-0.5">{cat.count.toLocaleString()} товаров</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Popular Products */}
|
||||||
|
<section className="max-w-7xl mx-auto px-4 pb-12">
|
||||||
|
<div className="flex items-center justify-between mb-6">
|
||||||
|
<h2 className="text-2xl font-black text-gray-900">Популярные товары</h2>
|
||||||
|
<a href="#" className="text-red-600 hover:text-red-700 text-sm font-semibold">
|
||||||
|
Все товары →
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{filteredProducts.length === 0 ? (
|
||||||
|
<div className="text-center py-16 text-gray-500">
|
||||||
|
<div className="text-5xl mb-3">🔍</div>
|
||||||
|
<div className="font-semibold">Товары не найдены</div>
|
||||||
|
<div className="text-sm mt-1">Попробуйте изменить запрос</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-5">
|
||||||
|
{filteredProducts.map((product) => (
|
||||||
|
<div
|
||||||
|
key={product.id}
|
||||||
|
className="bg-white rounded-xl shadow hover:shadow-lg transition-all border border-gray-100 hover:border-red-200 overflow-hidden flex flex-col"
|
||||||
|
>
|
||||||
|
<div className="relative h-44 overflow-hidden bg-gray-100">
|
||||||
|
<img
|
||||||
|
src={product.image}
|
||||||
|
alt={product.name}
|
||||||
|
className="w-full h-full object-cover hover:scale-105 transition-transform duration-300"
|
||||||
|
/>
|
||||||
|
{product.badge && (
|
||||||
|
<span
|
||||||
|
className={`absolute top-2 left-2 ${product.badgeColor} text-white text-[10px] font-bold px-2 py-0.5 rounded-full uppercase`}
|
||||||
|
>
|
||||||
|
{product.badge}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
onClick={() => toggleWishlist(product.id)}
|
||||||
|
className="absolute top-2 right-2 bg-white/80 hover:bg-white rounded-full w-7 h-7 flex items-center justify-center shadow transition-colors"
|
||||||
|
title="В избранное"
|
||||||
|
>
|
||||||
|
<span className={wishlist.includes(product.id) ? "text-red-500" : "text-gray-400"}>
|
||||||
|
♥
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="p-3 flex flex-col flex-1">
|
||||||
|
<div className="text-[10px] text-red-600 font-semibold uppercase mb-1">{product.brand}</div>
|
||||||
|
<div className="text-sm font-semibold text-gray-800 leading-snug mb-2 flex-1">
|
||||||
|
{product.name}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 mb-3">
|
||||||
|
<StarRating rating={product.rating} />
|
||||||
|
<span className="text-xs text-gray-400">({product.reviews})</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 mb-3">
|
||||||
|
<span className="text-lg font-black text-gray-900">
|
||||||
|
{product.price.toLocaleString()} ₽
|
||||||
|
</span>
|
||||||
|
{product.oldPrice && (
|
||||||
|
<span className="text-sm text-gray-400 line-through">
|
||||||
|
{product.oldPrice.toLocaleString()} ₽
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{product.oldPrice && (
|
||||||
|
<span className="text-xs text-red-600 font-bold bg-red-50 px-1.5 py-0.5 rounded">
|
||||||
|
−{Math.round((1 - product.price / product.oldPrice) * 100)}%
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => handleAddToCart(product.id)}
|
||||||
|
className={`w-full py-2 rounded-lg text-sm font-bold transition-all ${
|
||||||
|
addedToCart === product.id
|
||||||
|
? "bg-green-500 text-white"
|
||||||
|
: "bg-red-600 hover:bg-red-700 text-white"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{addedToCart === product.id ? "✔ Добавлено!" : "В корзину"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Brands */}
|
||||||
|
<section className="bg-white border-y border-gray-200 py-10">
|
||||||
|
<div className="max-w-7xl mx-auto px-4">
|
||||||
|
<h2 className="text-2xl font-black text-gray-900 mb-6 text-center">Бренды</h2>
|
||||||
|
<div className="flex flex-wrap items-center justify-center gap-3">
|
||||||
|
{brands.map((brand) => (
|
||||||
|
<button
|
||||||
|
key={brand}
|
||||||
|
className="border-2 border-gray-200 hover:border-red-500 text-gray-600 hover:text-red-600 font-bold px-5 py-2.5 rounded-lg transition-all text-sm"
|
||||||
|
>
|
||||||
|
{brand}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Advantages */}
|
||||||
|
<section className="max-w-7xl mx-auto px-4 py-14">
|
||||||
|
<h2 className="text-2xl font-black text-gray-900 mb-8 text-center">Почему выбирают нас</h2>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||||
|
{[
|
||||||
|
{
|
||||||
|
icon: "🚚",
|
||||||
|
title: "Быстрая доставка",
|
||||||
|
desc: "Доставляем по всей России за 1–5 рабочих дней. Бесплатно при заказе от 3000 ₽.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "✅",
|
||||||
|
title: "Только оригинал",
|
||||||
|
desc: "Гарантируем подлинность товара. Работаем только с официальными дистрибьюторами.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "🔄",
|
||||||
|
title: "Возврат 30 дней",
|
||||||
|
desc: "Если товар не подошёл — вернём деньги без лишних вопросов в течение 30 дней.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "🎧",
|
||||||
|
title: "Поддержка 24/7",
|
||||||
|
desc: "Наши специалисты помогут подобрать запчасти и ответят на любые вопросы.",
|
||||||
|
},
|
||||||
|
].map((adv) => (
|
||||||
|
<div
|
||||||
|
key={adv.title}
|
||||||
|
className="bg-white rounded-xl p-6 shadow border border-gray-100 text-center hover:border-red-200 hover:shadow-md transition-all"
|
||||||
|
>
|
||||||
|
<div className="text-4xl mb-3">{adv.icon}</div>
|
||||||
|
<div className="font-bold text-gray-900 mb-2">{adv.title}</div>
|
||||||
|
<div className="text-gray-500 text-sm leading-relaxed">{adv.desc}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Reviews */}
|
||||||
|
<section className="bg-gray-100 py-14">
|
||||||
|
<div className="max-w-7xl mx-auto px-4">
|
||||||
|
<div className="flex items-center justify-between mb-8">
|
||||||
|
<h2 className="text-2xl font-black text-gray-900">Отзывы покупателей</h2>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<StarRating rating={4.8} size="md" />
|
||||||
|
<span className="font-bold text-gray-800">4.8</span>
|
||||||
|
<span className="text-gray-500 text-sm">/ 5 (1 248 отзывов)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-5">
|
||||||
|
{reviews.map((rev, idx) => (
|
||||||
|
<div key={idx} className="bg-white rounded-xl p-5 shadow border border-gray-100">
|
||||||
|
<div className="flex items-center gap-3 mb-3">
|
||||||
|
<div className="w-10 h-10 rounded-full bg-red-600 text-white flex items-center justify-center font-bold text-lg">
|
||||||
|
{rev.name[0]}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="font-semibold text-gray-900 text-sm">{rev.name}</div>
|
||||||
|
<div className="text-gray-400 text-xs">{rev.date}</div>
|
||||||
|
</div>
|
||||||
|
<div className="ml-auto">
|
||||||
|
<StarRating rating={rev.rating} size="sm" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="text-gray-700 text-sm leading-relaxed mb-3">{rev.text}</p>
|
||||||
|
<div className="text-xs text-gray-400 bg-gray-50 rounded-lg px-3 py-1.5">
|
||||||
|
🚗 {rev.car}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Newsletter */}
|
||||||
|
<section className="bg-red-600 py-12">
|
||||||
|
<div className="max-w-2xl mx-auto px-4 text-center">
|
||||||
|
<h2 className="text-2xl font-black text-white mb-2">Не пропустите акции!</h2>
|
||||||
|
<p className="text-red-100 text-sm mb-6">
|
||||||
|
Подпишитесь на рассылку и получите скидку 10% на первый заказ
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-col sm:flex-row gap-2 max-w-md mx-auto">
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
placeholder="Ваш email..."
|
||||||
|
className="flex-1 px-4 py-3 rounded-lg text-sm outline-none text-gray-800"
|
||||||
|
/>
|
||||||
|
<button className="bg-gray-900 hover:bg-gray-800 text-white font-bold px-6 py-3 rounded-lg text-sm transition-colors whitespace-nowrap">
|
||||||
|
Подписаться
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<footer className="bg-gray-900 text-gray-400 py-12">
|
||||||
|
<div className="max-w-7xl mx-auto px-4">
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8 mb-8">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2 mb-4">
|
||||||
|
<div className="w-8 h-8 bg-red-600 rounded-lg flex items-center justify-center">
|
||||||
|
<span className="text-white font-black">А</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-white font-black text-sm">АвтоДетали Про</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs leading-relaxed text-gray-500">
|
||||||
|
Интернет-магазин автозапчастей. Более 50 000 товаров в наличии для всех марок автомобилей.
|
||||||
|
</p>
|
||||||
|
<div className="flex gap-3 mt-4">
|
||||||
|
{["VK", "TG", "OK"].map((s) => (
|
||||||
|
<a
|
||||||
|
key={s}
|
||||||
|
href="#"
|
||||||
|
className="w-8 h-8 bg-gray-700 hover:bg-red-600 rounded-lg flex items-center justify-center text-xs font-bold text-white transition-colors"
|
||||||
|
>
|
||||||
|
{s}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-white font-bold text-sm mb-4">Каталог</div>
|
||||||
|
<ul className="space-y-2 text-xs">
|
||||||
|
{["Двигатель", "Тормозная система", "Подвеска", "Масла и жидкости", "Фильтры", "Электрика", "Аксессуары"].map(
|
||||||
|
(item) => (
|
||||||
|
<li key={item}>
|
||||||
|
<a href="#" className="hover:text-white hover:text-red-400 transition-colors">
|
||||||
|
{item}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-white font-bold text-sm mb-4">Покупателям</div>
|
||||||
|
<ul className="space-y-2 text-xs">
|
||||||
|
{["Как оформить заказ", "Доставка и оплата", "Возврат товара", "Гарантия", "Отзывы", "Бонусная программа"].map(
|
||||||
|
(item) => (
|
||||||
|
<li key={item}>
|
||||||
|
<a href="#" className="hover:text-red-400 transition-colors">
|
||||||
|
{item}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-white font-bold text-sm mb-4">Контакты</div>
|
||||||
|
<ul className="space-y-2 text-xs">
|
||||||
|
<li>📞 +7 (800) 555-35-35 (бесплатно)</li>
|
||||||
|
<li>✉️ info@avtodetal-pro.ru</li>
|
||||||
|
<li>📍 г. Москва, ул. Автомобильная, 12</li>
|
||||||
|
<li>🕐 Пн–Пт: 9:00–20:00</li>
|
||||||
|
<li>🕐 Сб–Вс: 10:00–18:00</li>
|
||||||
|
</ul>
|
||||||
|
<div className="mt-4 text-xs">
|
||||||
|
<div className="text-white font-semibold mb-1">Принимаем оплату:</div>
|
||||||
|
<div className="flex gap-2 flex-wrap">
|
||||||
|
{["Visa", "MC", "МИР", "SBP"].map((pay) => (
|
||||||
|
<span key={pay} className="bg-gray-700 text-gray-300 text-[10px] px-2 py-1 rounded font-mono">
|
||||||
|
{pay}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="border-t border-gray-800 pt-6 text-xs text-center text-gray-600">
|
||||||
|
© 2025 АвтоДетали Про. Все права защищены. ИНН: 7712345678 | ОГРН: 1157746123456
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
@import "tailwindcss";
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { StrictMode } from "react";
|
||||||
|
import { createRoot } from "react-dom/client";
|
||||||
|
import "./index.css";
|
||||||
|
import App from "./App";
|
||||||
|
|
||||||
|
createRoot(document.getElementById("root")!).render(
|
||||||
|
<StrictMode>
|
||||||
|
<App />
|
||||||
|
</StrictMode>
|
||||||
|
);
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { clsx, type ClassValue } from "clsx";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs));
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"types": ["node"],
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Path mapping */
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["src", "vite.config.ts"]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
import path from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
import { viteSingleFile } from "vite-plugin-singlefile";
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react(), tailwindcss(), viteSingleFile()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@": path.resolve(__dirname, "src"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue