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